source('../env.R')
community_data = read_csv(filename(COMMUNITY_OUTPUT_DIR, 'community_assembly_metrics.csv'))
Rows: 792 Columns: 93── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr  (1): is_urban_threshold
dbl (92): city_id, mntd_normalised, mntd_actual, mntd_min, mntd_max, mntd_mean, mntd_sd, fd_normalised, fd_actual, fd_min, fd_max, fd_mean, fd_sd, FRic_normal...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
community_data$is_urban_threshold = factor(community_data$is_urban_threshold, levels = c('low', 'medium', 'high'), labels = c('Low', 'Medium', 'High'))
head(community_data)
colnames(community_data)
 [1] "city_id"                           "mntd_normalised"                   "mntd_actual"                       "mntd_min"                         
 [5] "mntd_max"                          "mntd_mean"                         "mntd_sd"                           "fd_normalised"                    
 [9] "fd_actual"                         "fd_min"                            "fd_max"                            "fd_mean"                          
[13] "fd_sd"                             "FRic_normalised"                   "FRic_actual"                       "FRic_min"                         
[17] "FRic_max"                          "FRic_mean"                         "FRic_sd"                           "mass_fd_normalised"               
[21] "mass_fd_actual"                    "mass_fd_min"                       "mass_fd_max"                       "mass_fd_mean"                     
[25] "mass_fd_sd"                        "mass_var_normalised"               "mass_var_actual"                   "mass_var_min"                     
[29] "mass_var_max"                      "mass_var_mean"                     "mass_var_sd"                       "mass_sdndr_normalised"            
[33] "mass_sdndr_actual"                 "mass_sdndr_min"                    "mass_sdndr_max"                    "mass_sdndr_mean"                  
[37] "mass_sdndr_sd"                     "locomotory_trait_fd_normalised"    "locomotory_trait_fd_actual"        "locomotory_trait_fd_min"          
[41] "locomotory_trait_fd_max"           "locomotory_trait_fd_mean"          "locomotory_trait_fd_sd"            "locomotory_trait_var_normalised"  
[45] "locomotory_trait_var_actual"       "locomotory_trait_var_min"          "locomotory_trait_var_max"          "locomotory_trait_var_mean"        
[49] "locomotory_trait_var_sd"           "locomotory_trait_sdndr_normalised" "locomotory_trait_sdndr_actual"     "locomotory_trait_sdndr_min"       
[53] "locomotory_trait_sdndr_max"        "locomotory_trait_sdndr_mean"       "locomotory_trait_sdndr_sd"         "trophic_trait_fd_normalised"      
[57] "trophic_trait_fd_actual"           "trophic_trait_fd_min"              "trophic_trait_fd_max"              "trophic_trait_fd_mean"            
[61] "trophic_trait_fd_sd"               "trophic_trait_var_normalised"      "trophic_trait_var_actual"          "trophic_trait_var_min"            
[65] "trophic_trait_var_max"             "trophic_trait_var_mean"            "trophic_trait_var_sd"              "trophic_trait_sdndr_normalised"   
[69] "trophic_trait_sdndr_actual"        "trophic_trait_sdndr_min"           "trophic_trait_sdndr_max"           "trophic_trait_sdndr_mean"         
[73] "trophic_trait_sdndr_sd"            "gape_width_fd_normalised"          "gape_width_fd_actual"              "gape_width_fd_min"                
[77] "gape_width_fd_max"                 "gape_width_fd_mean"                "gape_width_fd_sd"                  "gape_width_var_normalised"        
[81] "gape_width_var_actual"             "gape_width_var_min"                "gape_width_var_max"                "gape_width_var_mean"              
[85] "gape_width_var_sd"                 "gape_width_sdndr_normalised"       "gape_width_sdndr_actual"           "gape_width_sdndr_min"             
[89] "gape_width_sdndr_max"              "gape_width_sdndr_mean"             "gape_width_sdndr_sd"               "urban_pool_size"                  
[93] "is_urban_threshold"               

Join on realms

city_to_realm = read_csv(filename(CITY_DATA_OUTPUT_DIR, 'realms.csv'))
Rows: 342 Columns: 2── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (1): core_realm
dbl (1): city_id
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
community_data_with_realm = left_join(community_data, city_to_realm)
Joining with `by = join_by(city_id)`

Cities as points

city_points = st_centroid(read_sf(filename(CITY_DATA_OUTPUT_DIR, 'city_selection.shp')))
Warning: st_centroid assumes attributes are constant over geometries of x
city_points_low = city_points %>% left_join(community_data[community_data$is_urban_threshold == 'Low',])
Joining with `by = join_by(city_id)`
city_points_med = city_points %>% left_join(community_data[community_data$is_urban_threshold == 'Medium',])
Joining with `by = join_by(city_id)`
city_points_high = city_points %>% left_join(community_data[community_data$is_urban_threshold == 'High',])
Joining with `by = join_by(city_id)`
sf::sf_use_s2(FALSE)
Spherical geometry (s2) switched off
COUNTRY_BOUNDARIES = '/Users/james/Dropbox/PhD/WorldBank_countries_Admin0_10m/WB_countries_Admin0_10m.shp'
world_map = st_simplify(st_read(COUNTRY_BOUNDARIES), dTolerance = 0.02)
Reading layer `WB_countries_Admin0_10m' from data source `/Users/james/Dropbox/PhD/WorldBank_countries_Admin0_10m/WB_countries_Admin0_10m.shp' using driver `ESRI Shapefile'
Simple feature collection with 251 features and 52 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -180 ymin: -59.47275 xmax: 180 ymax: 83.6341
Geodetic CRS:  WGS 84
Warning: st_simplify does not correctly simplify longitude/latitude data, dTolerance needs to be in decimal degrees
normalised_colours_scale = scale_colour_gradient2(
    low = "darkgreen",
    mid = "yellow",
    high = "red",
    midpoint = 0.5,
    space = "Lab",
    na.value = "grey50",
    guide = "colourbar",
    aesthetics = "colour",
  )

Load community data, and create long format version

communities = read_csv(filename(COMMUNITY_OUTPUT_DIR, 'communities_for_analysis.csv'))
Rows: 2462 Columns: 9── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (5): city_name, jetz_species_name, seasonal, presence, origin
dbl (1): city_id
lgl (3): present_urban_high, present_urban_med, present_urban_low
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
communities_summary = communities %>% group_by(city_id) %>% summarise(
  regional_pool_size = n(), 
  urban_size_high = sum(present_urban_high), 
  urban_size_med = sum(present_urban_med), 
  urban_size_low = sum(present_urban_low)
)

communities_summary_long = bind_rows(
  communities_summary %>% rename(urban_pool_size = 'urban_size_high') %>% dplyr::select(city_id, regional_pool_size, urban_pool_size) %>% mutate(is_urban_threshold = 'High'),
  communities_summary %>% rename(urban_pool_size = 'urban_size_med') %>% dplyr::select(city_id, regional_pool_size, urban_pool_size) %>% mutate(is_urban_threshold = 'Medium'),
  communities_summary %>% rename(urban_pool_size = 'urban_size_low') %>% dplyr::select(city_id, regional_pool_size, urban_pool_size) %>% mutate(is_urban_threshold = 'Low')
)
communities_summary_long

Load trait data

traits = read_csv(filename(TAXONOMY_OUTPUT_DIR, 'traits_jetz.csv'))
Rows: 304 Columns: 5── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (1): jetz_species_name
dbl (4): gape_width, trophic_trait, locomotory_trait, mass
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(traits)

Load realm geo

st_crs(resolve)
Coordinate Reference System:
  User input: WGS 84 
  wkt:
GEOGCRS["WGS 84",
    DATUM["World Geodetic System 1984",
        ELLIPSOID["WGS 84",6378137,298.257223563,
            LENGTHUNIT["metre",1]]],
    PRIMEM["Greenwich",0,
        ANGLEUNIT["degree",0.0174532925199433]],
    CS[ellipsoidal,2],
        AXIS["latitude",north,
            ORDER[1],
            ANGLEUNIT["degree",0.0174532925199433]],
        AXIS["longitude",east,
            ORDER[2],
            ANGLEUNIT["degree",0.0174532925199433]],
    ID["EPSG",4326]]

Examine individual metrics

geom_normalised_histogram = function(name, gg, legend.position = "right") {
  gg + 
    geom_histogram(aes(fill = core_realm), binwidth = 0.1, position = "dodge") +
    geom_vline(aes(xintercept = 0.5), color = "#000000", size = 0.4) +
    geom_vline(aes(xintercept = 0), color = "#000000", size = 0.2, linetype = "dashed") +
    geom_vline(aes(xintercept = 1), color = "#000000", size = 0.2, linetype = "dashed") + 
    ylab("Number of cities") + xlab("Normalised Response") + ylim(c(0, 70)) +
    labs(title = name, fill = 'Realm') +
    facet_wrap(~ is_urban_threshold, ncol=1) + 
    theme_bw() +
    theme(legend.position=legend.position)
}
geom_map = function(map_sf, title) {
  norm_mntd_analysis_geo = ggplot() + 
    geom_sf(data = world_map, aes(geometry = geometry)) +
    map_sf +
    normalised_colours_scale +
    labs(title = title, colour = 'Normalised\nResponse')
}

MNTD

norm_mntd_analysis_plot = geom_normalised_histogram(
  'MNTD', 
  ggplot(community_data_with_realm, aes(mntd_normalised))
)
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
Please use `linewidth` instead.
norm_mntd_analysis_plot

norm_mntd_analysis_geo = geom_map(geom_sf(data = city_points_low, aes(color = mntd_normalised, geometry = geometry)), 'Low threshold')
norm_mntd_analysis_geo

ggarrange(norm_mntd_analysis_plot, 
          ggarrange(
            geom_normalised_histogram('MNTD', ggplot(community_data_with_realm, aes(mntd_normalised)), legend.position = "bottom"), 
            geom_map(geom_sf(data = city_points_med, aes(color = mntd_normalised, geometry = geometry)), 'Medium threshold'),
            geom_map(geom_sf(data = city_points_high, aes(color = mntd_normalised, geometry = geometry)), 'High threshold'),
            ncol=1, common.legend = T),
          ncol = 2)

ggsave(filename(FIGURES_OUTPUT_DIR, 'normalised_mntd.jpg'), width = 2500, height=2500, units = 'px')

FD

norm_fd_analysis_plot = geom_normalised_histogram(
  'FD', 
  ggplot(community_data_with_realm, aes(fd_normalised))
)
norm_fd_analysis_plot

norm_fd_analysis_geo = geom_map(geom_sf(data = city_points_low, aes(color = fd_normalised, geometry = geometry)), 'Low threshold')
norm_fd_analysis_geo

ggarrange(geom_normalised_histogram('FD', ggplot(community_data_with_realm, aes(fd_normalised)), legend.position = "bottom"), 
          ggarrange(
            norm_fd_analysis_geo, 
            geom_map(geom_sf(data = city_points_med, aes(color = fd_normalised, geometry = geometry)), 'Medium threshold'),
            geom_map(geom_sf(data = city_points_high, aes(color = fd_normalised, geometry = geometry)), 'High threshold'),
            ncol=1, common.legend = T),
          ncol = 2)

ggsave(filename(FIGURES_OUTPUT_DIR, 'normalised_fd.jpg'), width = 2500, height=2500, units = 'px')

FD - Locomotory

norm_fd_loco_analysis_plot = geom_normalised_histogram(
  'FD Locomotory', 
  ggplot(community_data_with_realm, aes(locomotory_trait_fd_normalised))
)
norm_fd_loco_analysis_plot

VAR - Locomotory

norm_var_loco_analysis_plot = geom_normalised_histogram(
  'Var Locomotory', 
  ggplot(community_data_with_realm, aes(locomotory_trait_var_normalised))
)
norm_var_loco_analysis_plot

FD - Trophic

norm_fd_trophic_analysis_plot = geom_normalised_histogram(
  'FD Trophic', 
  ggplot(community_data_with_realm, aes(trophic_trait_fd_normalised))
)
norm_fd_trophic_analysis_plot

VAR - Trophic

norm_var_trophic_analysis_plot = geom_normalised_histogram(
  'Var Trophic', 
  ggplot(community_data_with_realm, aes(trophic_trait_var_normalised))
)
norm_var_trophic_analysis_plot

FD - Gape Width

norm_fd_gape_analysis_plot = geom_normalised_histogram(
  'FD Gape Width', 
  ggplot(community_data_with_realm, aes(gape_width_fd_normalised))
)
norm_fd_gape_analysis_plot

VAR - Gape Width

norm_var_gape_analysis_plot = geom_normalised_histogram(
  'Var Gape Width', 
  ggplot(community_data_with_realm, aes(gape_width_var_normalised))
)
norm_var_gape_analysis_plot

FD - Mass

norm_fd_mass_analysis_plot = geom_normalised_histogram(
  'FD Mass', 
  ggplot(community_data_with_realm, aes(mass_fd_normalised))
)
norm_fd_mass_analysis_plot

VAR - Mass

norm_var_mass_analysis_plot = geom_normalised_histogram(
  'Var Mass', 
  ggplot(community_data_with_realm, aes(mass_var_normalised))
)
norm_var_mass_analysis_plot

Compare metrics against each other

ggplot(community_data_with_realm, aes(x = fd_normalised, y = mntd_normalised, colour = core_realm)) + 
  geom_point() +
  ylab("MNTD") + 
  xlab("FD") +
  theme_bw() +
  facet_wrap(~ is_urban_threshold, ncol = 2) + labs(color = "Realm")
ggsave(filename(FIGURES_OUTPUT_DIR, 'mntd_by_fd.jpg'))
Saving 7.29 x 4.51 in image

mntd_fd_analysis = community_data_with_realm %>% 
  dplyr::select(city_id, is_urban_threshold, mntd_normalised, fd_normalised) %>%
  left_join(communities_summary_long) %>%
  mutate(urban_pool_perc = urban_pool_size * 100 / regional_pool_size)
Joining with `by = join_by(city_id, is_urban_threshold)`
mntd_fd_analysis
ggpairs(mntd_fd_analysis %>% dplyr::filter(is_urban_threshold == 'Low') %>% dplyr::select(mntd_normalised, fd_normalised, regional_pool_size, urban_pool_size, urban_pool_perc))

ggpairs(mntd_fd_analysis %>% dplyr::filter(is_urban_threshold == 'Medium') %>% dplyr::select(mntd_normalised, fd_normalised, regional_pool_size, urban_pool_size, urban_pool_perc))
ggsave(filename(FIGURES_OUTPUT_DIR, 'mntd_by_fd_pairs_medium.jpg'))
Saving 7.29 x 4.51 in image

ggpairs(mntd_fd_analysis %>% dplyr::filter(is_urban_threshold == 'High') %>% dplyr::select(mntd_normalised, fd_normalised, regional_pool_size, urban_pool_size, urban_pool_perc))

Plot Urban/Regional Traits

communities_with_traits = communities %>% left_join(traits) %>% left_join(city_to_realm) %>% filter(core_realm != 'Oceania')
Joining with `by = join_by(jetz_species_name)`Joining with `by = join_by(city_id)`
head(communities_with_traits)
realms = unique(communities_with_traits$core_realm)
realms
[1] "Nearctic"    "Neotropic"   "Palearctic"  "Afrotropic"  "Indomalayan" "Australasia"
communities_with_traits_long = bind_rows(
  communities_with_traits %>% dplyr::select(gape_width, trophic_trait, locomotory_trait, mass, core_realm) %>% mutate(record_type = 'Regional'),
  communities_with_traits %>% filter(present_urban_low) %>% dplyr::select(gape_width, trophic_trait, locomotory_trait, mass, core_realm) %>% mutate(record_type = 'Urban Low Threshold'),
  communities_with_traits %>% filter(present_urban_med) %>% dplyr::select(gape_width, trophic_trait, locomotory_trait, mass, core_realm) %>% mutate(record_type = 'Urban Medium Threshold'),
  communities_with_traits %>% filter(present_urban_high) %>% dplyr::select(gape_width, trophic_trait, locomotory_trait, mass, core_realm) %>% mutate(record_type = 'Urban High Threshold')
)
communities_with_traits_long$record_type = factor(communities_with_traits_long$record_type, levels = c('Regional', 'Urban Low Threshold', 'Urban Medium Threshold', 'Urban High Threshold'))

head(communities_with_traits_long)
convex_hull_loco_trophic_per_realm = function(filtered_df, realms) {
  result = data.frame()
  
  for (realm in realms) {
    result = rbind(result, 
      filtered_df %>% 
        filter(core_realm == realm) %>% 
        slice(chull(trophic_trait, locomotory_trait)) %>% 
        dplyr::select(trophic_trait, locomotory_trait) %>%
        mutate(core_realm = realm)
    )
  }
  
  result
}
regional_hull = convex_hull_loco_trophic_per_realm(communities_with_traits, realms)
urban_hull_high = convex_hull_loco_trophic_per_realm(communities_with_traits %>% filter(present_urban_high), realms)
urban_hull_med = convex_hull_loco_trophic_per_realm(communities_with_traits %>% filter(present_urban_med), realms)
urban_hull_low = convex_hull_loco_trophic_per_realm(communities_with_traits %>% filter(present_urban_low), realms)

ggplot(data = communities_with_traits_long, aes(x = trophic_trait, y = locomotory_trait)) +
  geom_polygon(data = regional_hull, alpha = 0.1, fill = "green", color="green") +
  geom_polygon(data = urban_hull_low, alpha = 0.15, fill = "yellow", color="yellow") +
  geom_polygon(data = urban_hull_med, alpha = 0.2, fill = "orange", color = "orange") +
  geom_polygon(data = urban_hull_high, alpha = 0.25, fill = "red", color = "red") +
  geom_point(aes(colour = record_type)) +
  theme_bw() + scale_color_manual(values=c("green", "yellow", "orange", "red")) +
  xlab('Trophic Trait') + ylab('Locomotory Trait') + 
  theme(legend.position="bottom") + 
  facet_wrap(~ core_realm)
ggsave(filename(FIGURES_OUTPUT_DIR, 'traits_by_realm_loco_trophic.jpg'))
Saving 7.29 x 4.51 in image

convex_hull_gape_mass_per_realm = function(filtered_df, realms) {
  result = data.frame()
  
  for (realm in realms) {
    result = rbind(result, 
      filtered_df %>% 
        filter(core_realm == realm) %>% 
        slice(chull(gape_width, mass)) %>% 
        dplyr::select(gape_width, mass) %>%
        mutate(core_realm = realm)
    )
  }
  
  result
}
regional_hull_gm = convex_hull_gape_mass_per_realm(communities_with_traits, realms)
urban_hull_high_gm = convex_hull_gape_mass_per_realm(communities_with_traits %>% filter(present_urban_high), realms)
urban_hull_med_gm = convex_hull_gape_mass_per_realm(communities_with_traits %>% filter(present_urban_med), realms)
urban_hull_low_gm = convex_hull_gape_mass_per_realm(communities_with_traits %>% filter(present_urban_low), realms)

ggplot(data = communities_with_traits_long, aes(x = gape_width, y = mass)) +
  geom_polygon(data = regional_hull_gm, alpha = 0.1, fill = "green", color="green") +
  geom_polygon(data = urban_hull_low_gm, alpha = 0.15, fill = "yellow", color="yellow") +
  geom_polygon(data = urban_hull_med_gm, alpha = 0.2, fill = "orange", color = "orange") +
  geom_polygon(data = urban_hull_high_gm, alpha = 0.25, fill = "red", color = "red") +
  geom_point(aes(colour = record_type)) +
  theme_bw() + scale_color_manual(values=c("green", "yellow", "orange", "red")) +
  xlab('Gape Width') + ylab('Mass') + 
  theme(legend.position="bottom") + 
  facet_wrap(~ core_realm)
ggsave(filename(FIGURES_OUTPUT_DIR, 'traits_by_realm_gape_mass.jpg'))
Saving 7.29 x 4.51 in image

Plot Urban/Regional Phylogeny

phylo_tree = read.tree(filename(TAXONOMY_OUTPUT_DIR, 'phylogeny.tre'))
ggtree(phylo_tree, layout='circular')

plot_phylo = function(realm, community_df = communities_with_traits) {
  species_df = community_df %>% filter(core_realm == realm) %>% group_by(jetz_species_name) %>% summarise(
    regional_pools = n(),
    urban_pools_high = sum(present_urban_high),
    urban_pools_medium = sum(present_urban_med),
    urban_pools_low = sum(present_urban_low)
  ) %>% mutate(
    urban_tolerence_high = urban_pools_high / regional_pools,
    urban_tolerence_med = urban_pools_medium / regional_pools,
    urban_tolerence_low = urban_pools_low / regional_pools
  )
  
  tree_cropped <- ladderize(drop.tip(phylo_tree, setdiff(phylo_tree$tip.label, species_df$jetz_species_name)))
  
  p = ggtree(tree_cropped) + geom_tiplab(align=T) 
  p1 = facet_plot(p, panel='High', data=species_df, geom=geom_segment, aes(x=0, xend=urban_tolerence_high, y=y, yend=y), size=3, color='red') 
  p2 = facet_plot(p1, panel='Medium', data=species_df, geom=geom_segment, aes(x=0, xend=urban_tolerence_med, y=y, yend=y), size=3, color='orange') 
  p3 = facet_plot(p2, panel='Low', data=species_df, geom=geom_segment, aes(x=0, xend=urban_tolerence_low, y=y, yend=y), size=3, color='yellow') 
  
  facet_widths(p3 + xlim_tree(60) + theme_tree2(), c(Tree = 5)) + labs(title = realm, subtitle = 'Urban tolerance')
}
realms
[1] "Nearctic"    "Neotropic"   "Palearctic"  "Afrotropic"  "Indomalayan" "Australasia"
plot_phylo('Nearctic')

ggsave(filename(FIGURES_OUTPUT_DIR, 'phylogeny_nearctic.jpg'))
Saving 7.29 x 4.51 in image

plot_phylo('Neotropic')

ggsave(filename(FIGURES_OUTPUT_DIR, 'phylogeny_neotropic.jpg'), width=2187, units="px")
Saving 2187 x 3000 px image
plot_phylo('Palearctic')

ggsave(filename(FIGURES_OUTPUT_DIR, 'phylogeny_palearctic.jpg'))
Saving 7.29 x 4.51 in image

plot_phylo('Afrotropic')

ggsave(filename(FIGURES_OUTPUT_DIR, 'phylogeny_afrotropic.jpg'))
Saving 7.29 x 4.51 in image

plot_phylo('Australasia')

ggsave(filename(FIGURES_OUTPUT_DIR, 'phylogeny_australasia.jpg'))
Saving 7.29 x 4.51 in image

Species in communities

It seems reasonable to expect that cities with simialr regional pools will have similar species entering the city, and thus a similar response to urbanisation.

to_species_matrix = function(filtered_communities) {
  filtered_communities %>% 
    dplyr::select(city_id, jetz_species_name) %>% 
    distinct() %>%
    mutate(present = TRUE) %>% 
    pivot_wider(
      names_from = jetz_species_name, 
      values_from = "present", 
      values_fill = list(present = F)
    ) %>% 
    tibble::column_to_rownames(var='city_id')
}
community_nmds = function(filtered_communities) {
  species_matrix = to_species_matrix(filtered_communities)
  nmds <- metaMDS(species_matrix, k=2, trymax = 30)
  nmds_result = data.frame(scores(nmds))
  nmds_result$city_id = as.double(rownames(scores(nmds)))
  rownames(nmds_result) = NULL
  
  nmds_result %>%
    left_join(city_points[,c('city_id', 'city_nm')])
}
plot_nmds = function(plot_data, clusters, realm, plot_is_urban_threshold) {
  pd = plot_data %>%
    left_join(community_data) %>%
    filter(is_urban_threshold == plot_is_urban_threshold)
  
  ggplot(pd, aes(x = NMDS1, y = NMDS2)) + 
    geom_polygon(data = clusters, aes(fill = cluster), alpha = 0.2) +
    geom_jitter(aes(colour = mntd_normalised, size = fd_normalised)) +
    normalised_colours_scale +
    geom_text_repel(aes(label = city_nm), max.overlaps = 25) +
    labs(
      title = realm, 
      subtitle = paste('Regional pool, with urban community values using', plot_is_urban_threshold, 'threshold'), 
      color = 'MNTD Normalised', 
      size = 'FD Normalised')
}

https://www.datacamp.com/tutorial/k-means-clustering-r

scree_plot = function(community_nmds_data) {
  # Decide how many clusters to look at
  n_clusters <- 10
  
  # Initialize total within sum of squares error: wss
  wss <- numeric(n_clusters)
  
  set.seed(123)
  
  # Look over 1 to n possible clusters
  for (i in 1:n_clusters) {
    # Fit the model: km.out
    km.out <- kmeans(community_nmds_data[,c('NMDS1','NMDS2')], centers = i, nstart = 20)
    # Save the within cluster sum of squares
    wss[i] <- km.out$tot.withinss
  }
  
  # Produce a scree plot
  wss_df <- tibble(clusters = 1:n_clusters, wss = wss)
   
  scree_plot <- ggplot(wss_df, aes(x = clusters, y = wss, group = 1)) +
      geom_point(size = 4)+
      geom_line() +
      scale_x_continuous(breaks = c(2, 4, 6, 8, 10)) +
      xlab('Number of clusters')
  scree_plot
}
convex_hull_by_cluster = function(community_df) {
  result = data.frame()
  
  clusters = unique(community_df$cluster)
  for (cluster_i in clusters) {
    result = rbind(result, 
      community_df %>% 
        filter(cluster == cluster_i) %>% 
        slice(chull(NMDS1, NMDS2)) %>% 
        dplyr::select(NMDS1, NMDS2, cluster) 
    )
  }
  
  result
}

Nearctic

nearctic_communities = community_nmds(communities_with_traits %>% filter(core_realm == 'Nearctic'))    
Run 0 stress 0.06772313 
Run 1 stress 0.09925753 
Run 2 stress 0.0814111 
Run 3 stress 0.0714024 
Run 4 stress 0.08477133 
Run 5 stress 0.06792384 
... Procrustes: rmse 0.002677399  max resid 0.01592546 
Run 6 stress 0.07140237 
Run 7 stress 0.09410676 
Run 8 stress 0.06772314 
... Procrustes: rmse 0.000007756193  max resid 0.00001712457 
... Similar to previous best
Run 9 stress 0.0847715 
Run 10 stress 0.06772321 
... Procrustes: rmse 0.00005153412  max resid 0.00008951089 
... Similar to previous best
Run 11 stress 0.06792388 
... Procrustes: rmse 0.00267859  max resid 0.01598036 
Run 12 stress 0.0677232 
... Procrustes: rmse 0.00004962282  max resid 0.00008595323 
... Similar to previous best
Run 13 stress 0.08193052 
Run 14 stress 0.09279085 
Run 15 stress 0.08494123 
Run 16 stress 0.09422939 
Run 17 stress 0.07122361 
Run 18 stress 0.08521544 
Run 19 stress 0.07427005 
Run 20 stress 0.06792383 
... Procrustes: rmse 0.002676712  max resid 0.01589483 
*** Solution reached
Joining with `by = join_by(city_id)`
nearctic_communities
scree_plot(nearctic_communities)
Warning: Quick-TRANSfer stage steps exceeded maximum (= 3300)Warning: Quick-TRANSfer stage steps exceeded maximum (= 3300)Warning: Quick-TRANSfer stage steps exceeded maximum (= 3300)Warning: Quick-TRANSfer stage steps exceeded maximum (= 3300)Warning: did not converge in 10 iterationsWarning: Quick-TRANSfer stage steps exceeded maximum (= 3300)Warning: Quick-TRANSfer stage steps exceeded maximum (= 3300)Warning: did not converge in 10 iterationsWarning: Quick-TRANSfer stage steps exceeded maximum (= 3300)Warning: Quick-TRANSfer stage steps exceeded maximum (= 3300)Warning: did not converge in 10 iterationsWarning: Quick-TRANSfer stage steps exceeded maximum (= 3300)Warning: did not converge in 10 iterationsWarning: Quick-TRANSfer stage steps exceeded maximum (= 3300)Warning: did not converge in 10 iterationsWarning: Quick-TRANSfer stage steps exceeded maximum (= 3300)Warning: Quick-TRANSfer stage steps exceeded maximum (= 3300)Warning: did not converge in 10 iterationsWarning: Quick-TRANSfer stage steps exceeded maximum (= 3300)Warning: Quick-TRANSfer stage steps exceeded maximum (= 3300)Warning: Quick-TRANSfer stage steps exceeded maximum (= 3300)Warning: did not converge in 10 iterationsWarning: Quick-TRANSfer stage steps exceeded maximum (= 3300)

nearctic_clusters <- kmeans(nearctic_communities[,c('NMDS1', 'NMDS2')], centers = 3, nstart = 20)
nearctic_communities$cluster = as.factor(nearctic_clusters$cluster)
ggplot(nearctic_communities, aes(x = NMDS1, y = NMDS2, colour = cluster)) + geom_point()

nearctic_cluster_hulls = convex_hull_by_cluster(nearctic_communities)
plot_nmds(nearctic_communities, nearctic_cluster_hulls, 'Nearctic', 'High')
Joining with `by = join_by(city_id)`

plot_nmds(nearctic_communities, nearctic_cluster_hulls, 'Nearctic', 'Medium')
Joining with `by = join_by(city_id)`

plot_nmds(nearctic_communities, nearctic_cluster_hulls, 'Nearctic', 'Low')
Joining with `by = join_by(city_id)`

species_in_neartic = nearctic_communities %>% dplyr::select(city_id, cluster) %>% left_join(communities_with_traits)
Joining with `by = join_by(city_id)`
plot_phylo('Nearctic', species_in_neartic %>% filter(cluster == 1))

ggplot() +
  geom_sf(data = resolve_nearctic, aes(geometry = geometry), alpha = 0.2) +
  geom_sf(data = nearctic_communities %>% filter(cluster == 1) %>% left_join(city_points_med), aes(color = mntd_normalised, geometry = geometry)) +
  normalised_colours_scale + theme_bw()
Joining with `by = join_by(city_id, city_nm, geometry)`

plot_phylo('Nearctic', species_in_neartic %>% filter(cluster == 2))

ggplot() +
  geom_sf(data = resolve_nearctic, aes(geometry = geometry), alpha = 0.2) +
  geom_sf(data = nearctic_communities %>% filter(cluster == 2) %>% left_join(city_points_med), aes(color = mntd_normalised, geometry = geometry)) +
  normalised_colours_scale + theme_bw()
Joining with `by = join_by(city_id, city_nm, geometry)`

ggplot() +
  geom_sf(data = resolve_nearctic, aes(geometry = geometry), alpha = 0.2) +
  geom_sf(data = nearctic_communities %>% filter(cluster == 3) %>% left_join(city_points_med), aes(color = mntd_normalised, geometry = geometry)) +
  normalised_colours_scale + theme_bw()
Joining with `by = join_by(city_id, city_nm, geometry)`

Neotropic

neotropic_communities = community_nmds(communities_with_traits %>% filter(core_realm == 'Neotropic'))    
Run 0 stress 0.134619 
Run 1 stress 0.1344509 
... New best solution
... Procrustes: rmse 0.006928659  max resid 0.0457892 
Run 2 stress 0.1369387 
Run 3 stress 0.134619 
... Procrustes: rmse 0.006940788  max resid 0.04575861 
Run 4 stress 0.1344509 
... New best solution
... Procrustes: rmse 0.00004403156  max resid 0.0001387448 
... Similar to previous best
Run 5 stress 0.1346406 
... Procrustes: rmse 0.007192008  max resid 0.0457332 
Run 6 stress 0.1346226 
... Procrustes: rmse 0.007280471  max resid 0.04573126 
Run 7 stress 0.136377 
Run 8 stress 0.1348046 
... Procrustes: rmse 0.006663518  max resid 0.04610571 
Run 9 stress 0.1406945 
Run 10 stress 0.1346226 
... Procrustes: rmse 0.007291867  max resid 0.04571052 
Run 11 stress 0.134619 
... Procrustes: rmse 0.006932652  max resid 0.04571971 
Run 12 stress 0.134433 
... New best solution
... Procrustes: rmse 0.001186888  max resid 0.006991429 
... Similar to previous best
Run 13 stress 0.1348237 
... Procrustes: rmse 0.006651112  max resid 0.04607972 
Run 14 stress 0.1346405 
... Procrustes: rmse 0.007243159  max resid 0.04569399 
Run 15 stress 0.1406945 
Run 16 stress 0.134619 
... Procrustes: rmse 0.006830928  max resid 0.04574414 
Run 17 stress 0.1344509 
... Procrustes: rmse 0.001186553  max resid 0.00692233 
... Similar to previous best
Run 18 stress 0.141222 
Run 19 stress 0.1346207 
... Procrustes: rmse 0.006999734  max resid 0.04555 
Run 20 stress 0.134433 
... New best solution
... Procrustes: rmse 0.00002505145  max resid 0.00008159977 
... Similar to previous best
*** Solution reached
Joining with `by = join_by(city_id)`
neotropic_communities
scree_plot(neotropic_communities)

neotropic_clusters <- kmeans(neotropic_communities[,c('NMDS1', 'NMDS2')], centers = 3, nstart = 20)
neotropic_communities$cluster = as.factor(neotropic_clusters$cluster)
ggplot(neotropic_communities, aes(x = NMDS1, y = NMDS2, colour = cluster)) + geom_point()

neotropic_cluster_hulls = convex_hull_by_cluster(neotropic_communities)
plot_nmds(neotropic_communities, neotropic_cluster_hulls, 'Neotropic', 'High')
Joining with `by = join_by(city_id)`

plot_nmds(neotropic_communities, neotropic_cluster_hulls, 'Neotropic', 'Medium')
Joining with `by = join_by(city_id)`

plot_nmds(neotropic_communities, neotropic_cluster_hulls, 'Neotropic', 'Low')
Joining with `by = join_by(city_id)`

species_in_neotropic = neotropic_communities %>% dplyr::select(city_id, cluster) %>% left_join(communities_with_traits)
Joining with `by = join_by(city_id)`
plot_phylo('Neotropic', species_in_neotropic %>% filter(cluster == 1))

resolve_neotropic = resolve %>% filter(REALM == 'Neotropic')

ggplot() +
  geom_sf(data = resolve_neotropic, aes(geometry = geometry), alpha = 0.2) +
  geom_sf(data = neotropic_communities %>% filter(cluster == 1) %>% left_join(city_points_med), aes(color = mntd_normalised, geometry = geometry)) +
  normalised_colours_scale + theme_bw()
Joining with `by = join_by(city_id, city_nm, geometry)`

plot_phylo('Neotropic', species_in_neotropic %>% filter(cluster == 2))

ggplot() +
  geom_sf(data = resolve_neotropic, aes(geometry = geometry), alpha = 0.2) +
  geom_sf(data = neotropic_communities %>% filter(cluster == 2) %>% left_join(city_points_med), aes(color = mntd_normalised, geometry = geometry)) +
  normalised_colours_scale + theme_bw()
Joining with `by = join_by(city_id, city_nm, geometry)`

plot_phylo('Neotropic', species_in_neotropic %>% filter(cluster == 3))

ggplot() +
  geom_sf(data = resolve_neotropic, aes(geometry = geometry), alpha = 0.2) +
  geom_sf(data = neotropic_communities %>% filter(cluster == 3) %>% left_join(city_points_med), aes(color = mntd_normalised, geometry = geometry)) +
  normalised_colours_scale + theme_bw()
Joining with `by = join_by(city_id, city_nm, geometry)`

Palearctic

Afrotropic

Indomalayan

indomalayan_communities = community_nmds(communities_with_traits %>% filter(core_realm == 'Indomalayan'))    
Run 0 stress 0.1193009 
Run 1 stress 0.1285877 
Run 2 stress 0.1472786 
Run 3 stress 0.1289009 
Run 4 stress 0.1240017 
Run 5 stress 0.157254 
Run 6 stress 0.1509529 
Run 7 stress 0.161852 
Run 8 stress 0.1558148 
Run 9 stress 0.1239373 
Run 10 stress 0.1165214 
... New best solution
... Procrustes: rmse 0.03581633  max resid 0.2499892 
Run 11 stress 0.1151087 
... New best solution
... Procrustes: rmse 0.02627242  max resid 0.2457618 
Run 12 stress 0.1167036 
Run 13 stress 0.1220414 
Run 14 stress 0.1229737 
Run 15 stress 0.1382712 
Run 16 stress 0.1472537 
Run 17 stress 0.1502501 
Run 18 stress 0.1666794 
Run 19 stress 0.1398437 
Run 20 stress 0.1448979 
Run 21 stress 0.1252406 
Run 22 stress 0.1443181 
Run 23 stress 0.1156951 
Run 24 stress 0.1618047 
Run 25 stress 0.1512284 
Run 26 stress 0.1245209 
Run 27 stress 0.1166941 
Run 28 stress 0.1156757 
Run 29 stress 0.1257237 
Run 30 stress 0.1169841 
*** No convergence -- monoMDS stopping criteria:
     1: no. of iterations >= maxit
    26: stress ratio > sratmax
     3: scale factor of the gradient < sfgrmin
Joining with `by = join_by(city_id)`
indomalayan_communities
scree_plot(indomalayan_communities)

indomalayan_clusters <- kmeans(indomalayan_communities[,c('NMDS1', 'NMDS2')], centers = 4, nstart = 20)
indomalayan_communities$cluster = as.factor(indomalayan_clusters$cluster)
ggplot(indomalayan_communities, aes(x = NMDS1, y = NMDS2, colour = cluster)) + geom_point()

indomalayan_cluster_hulls = convex_hull_by_cluster(indomalayan_communities)
plot_nmds(indomalayan_communities, indomalayan_cluster_hulls, 'Indomalayan', 'High')
Joining with `by = join_by(city_id)`

plot_nmds(indomalayan_communities, indomalayan_cluster_hulls, 'Indomalayan', 'Medium')
Joining with `by = join_by(city_id)`

plot_nmds(indomalayan_communities, indomalayan_cluster_hulls, 'Indomalayan', 'Low')
Joining with `by = join_by(city_id)`

species_in_indomalayan = indomalayan_communities %>% dplyr::select(city_id, cluster) %>% left_join(communities_with_traits)
Joining with `by = join_by(city_id)`
plot_phylo('Indomalayan', species_in_indomalayan %>% filter(cluster == 1))

resolve_indomalayan = resolve %>% filter(REALM == 'Indomalayan')

ggplot() +
  geom_sf(data = resolve_indomalayan, aes(geometry = geometry), alpha = 0.2) +
  geom_sf(data = indomalayan_communities %>% filter(cluster == 1) %>% left_join(city_points_med), aes(color = mntd_normalised, geometry = geometry)) +
  normalised_colours_scale + theme_bw()
Joining with `by = join_by(city_id, city_nm, geometry)`

ggplot() +
  geom_sf(data = resolve_indomalayan, aes(geometry = geometry), alpha = 0.2) +
  geom_sf(data = indomalayan_communities %>% filter(cluster == 2) %>% left_join(city_points_med), aes(color = mntd_normalised, geometry = geometry)) +
  normalised_colours_scale + theme_bw()
Joining with `by = join_by(city_id, city_nm, geometry)`

plot_phylo('Indomalayan', species_in_indomalayan %>% filter(cluster == 3))

ggplot() +
  geom_sf(data = resolve_indomalayan, aes(geometry = geometry), alpha = 0.2) +
  geom_sf(data = indomalayan_communities %>% filter(cluster == 3) %>% left_join(city_points_med), aes(color = mntd_normalised, geometry = geometry)) +
  normalised_colours_scale + theme_bw()
Joining with `by = join_by(city_id, city_nm, geometry)`

plot_phylo('Indomalayan', species_in_indomalayan %>% filter(cluster == 4))

ggplot() +
  geom_sf(data = resolve_indomalayan, aes(geometry = geometry), alpha = 0.2) +
  geom_sf(data = indomalayan_communities %>% filter(cluster == 4) %>% left_join(city_points_med), aes(color = mntd_normalised, geometry = geometry)) +
  normalised_colours_scale + theme_bw()
Joining with `by = join_by(city_id, city_nm, geometry)`

Australasia

LS0tCnRpdGxlOiAiTWV0cmljcyBmb3IgYXNzZXNzaW5nIGNvbW11bml0eSBhc3NlbWJseSBwcm9jZXNzZXMiCm91dHB1dDogaHRtbF9ub3RlYm9vawpiaWJsaW9ncmFwaHk6IC4uL3JlZi5iaWIgIAotLS0KCmBgYHtyfQpzb3VyY2UoJy4uL2Vudi5SJykKYGBgCgpgYGB7cn0KY29tbXVuaXR5X2RhdGEgPSByZWFkX2NzdihmaWxlbmFtZShDT01NVU5JVFlfT1VUUFVUX0RJUiwgJ2NvbW11bml0eV9hc3NlbWJseV9tZXRyaWNzLmNzdicpKQpjb21tdW5pdHlfZGF0YSRpc191cmJhbl90aHJlc2hvbGQgPSBmYWN0b3IoY29tbXVuaXR5X2RhdGEkaXNfdXJiYW5fdGhyZXNob2xkLCBsZXZlbHMgPSBjKCdsb3cnLCAnbWVkaXVtJywgJ2hpZ2gnKSwgbGFiZWxzID0gYygnTG93JywgJ01lZGl1bScsICdIaWdoJykpCmhlYWQoY29tbXVuaXR5X2RhdGEpCmNvbG5hbWVzKGNvbW11bml0eV9kYXRhKQpgYGAKCkpvaW4gb24gcmVhbG1zCmBgYHtyfQpjaXR5X3RvX3JlYWxtID0gcmVhZF9jc3YoZmlsZW5hbWUoQ0lUWV9EQVRBX09VVFBVVF9ESVIsICdyZWFsbXMuY3N2JykpCmNvbW11bml0eV9kYXRhX3dpdGhfcmVhbG0gPSBsZWZ0X2pvaW4oY29tbXVuaXR5X2RhdGEsIGNpdHlfdG9fcmVhbG0pCmBgYAoKQ2l0aWVzIGFzIHBvaW50cwpgYGB7cn0KY2l0eV9wb2ludHMgPSBzdF9jZW50cm9pZChyZWFkX3NmKGZpbGVuYW1lKENJVFlfREFUQV9PVVRQVVRfRElSLCAnY2l0eV9zZWxlY3Rpb24uc2hwJykpKQoKY2l0eV9wb2ludHNfbG93ID0gY2l0eV9wb2ludHMgJT4lIGxlZnRfam9pbihjb21tdW5pdHlfZGF0YVtjb21tdW5pdHlfZGF0YSRpc191cmJhbl90aHJlc2hvbGQgPT0gJ0xvdycsXSkKY2l0eV9wb2ludHNfbWVkID0gY2l0eV9wb2ludHMgJT4lIGxlZnRfam9pbihjb21tdW5pdHlfZGF0YVtjb21tdW5pdHlfZGF0YSRpc191cmJhbl90aHJlc2hvbGQgPT0gJ01lZGl1bScsXSkKY2l0eV9wb2ludHNfaGlnaCA9IGNpdHlfcG9pbnRzICU+JSBsZWZ0X2pvaW4oY29tbXVuaXR5X2RhdGFbY29tbXVuaXR5X2RhdGEkaXNfdXJiYW5fdGhyZXNob2xkID09ICdIaWdoJyxdKQpgYGAKICAKYGBge3J9CnNmOjpzZl91c2VfczIoRkFMU0UpCkNPVU5UUllfQk9VTkRBUklFUyA9ICcvVXNlcnMvamFtZXMvRHJvcGJveC9QaEQvV29ybGRCYW5rX2NvdW50cmllc19BZG1pbjBfMTBtL1dCX2NvdW50cmllc19BZG1pbjBfMTBtLnNocCcKd29ybGRfbWFwID0gc3Rfc2ltcGxpZnkoc3RfcmVhZChDT1VOVFJZX0JPVU5EQVJJRVMpLCBkVG9sZXJhbmNlID0gMC4wMikKYGBgCmBgYHtyfQpub3JtYWxpc2VkX2NvbG91cnNfc2NhbGUgPSBzY2FsZV9jb2xvdXJfZ3JhZGllbnQyKAogICAgbG93ID0gImRhcmtncmVlbiIsCiAgICBtaWQgPSAieWVsbG93IiwKICAgIGhpZ2ggPSAicmVkIiwKICAgIG1pZHBvaW50ID0gMC41LAogICAgc3BhY2UgPSAiTGFiIiwKICAgIG5hLnZhbHVlID0gImdyZXk1MCIsCiAgICBndWlkZSA9ICJjb2xvdXJiYXIiLAogICAgYWVzdGhldGljcyA9ICJjb2xvdXIiLAogICkKYGBgCgpMb2FkIGNvbW11bml0eSBkYXRhLCBhbmQgY3JlYXRlIGxvbmcgZm9ybWF0IHZlcnNpb24KYGBge3J9CmNvbW11bml0aWVzID0gcmVhZF9jc3YoZmlsZW5hbWUoQ09NTVVOSVRZX09VVFBVVF9ESVIsICdjb21tdW5pdGllc19mb3JfYW5hbHlzaXMuY3N2JykpCgpjb21tdW5pdGllc19zdW1tYXJ5ID0gY29tbXVuaXRpZXMgJT4lIGdyb3VwX2J5KGNpdHlfaWQpICU+JSBzdW1tYXJpc2UoCiAgcmVnaW9uYWxfcG9vbF9zaXplID0gbigpLCAKICB1cmJhbl9zaXplX2hpZ2ggPSBzdW0ocHJlc2VudF91cmJhbl9oaWdoKSwgCiAgdXJiYW5fc2l6ZV9tZWQgPSBzdW0ocHJlc2VudF91cmJhbl9tZWQpLCAKICB1cmJhbl9zaXplX2xvdyA9IHN1bShwcmVzZW50X3VyYmFuX2xvdykKKQoKY29tbXVuaXRpZXNfc3VtbWFyeV9sb25nID0gYmluZF9yb3dzKAogIGNvbW11bml0aWVzX3N1bW1hcnkgJT4lIHJlbmFtZSh1cmJhbl9wb29sX3NpemUgPSAndXJiYW5fc2l6ZV9oaWdoJykgJT4lIGRwbHlyOjpzZWxlY3QoY2l0eV9pZCwgcmVnaW9uYWxfcG9vbF9zaXplLCB1cmJhbl9wb29sX3NpemUpICU+JSBtdXRhdGUoaXNfdXJiYW5fdGhyZXNob2xkID0gJ0hpZ2gnKSwKICBjb21tdW5pdGllc19zdW1tYXJ5ICU+JSByZW5hbWUodXJiYW5fcG9vbF9zaXplID0gJ3VyYmFuX3NpemVfbWVkJykgJT4lIGRwbHlyOjpzZWxlY3QoY2l0eV9pZCwgcmVnaW9uYWxfcG9vbF9zaXplLCB1cmJhbl9wb29sX3NpemUpICU+JSBtdXRhdGUoaXNfdXJiYW5fdGhyZXNob2xkID0gJ01lZGl1bScpLAogIGNvbW11bml0aWVzX3N1bW1hcnkgJT4lIHJlbmFtZSh1cmJhbl9wb29sX3NpemUgPSAndXJiYW5fc2l6ZV9sb3cnKSAlPiUgZHBseXI6OnNlbGVjdChjaXR5X2lkLCByZWdpb25hbF9wb29sX3NpemUsIHVyYmFuX3Bvb2xfc2l6ZSkgJT4lIG11dGF0ZShpc191cmJhbl90aHJlc2hvbGQgPSAnTG93JykKKQpjb21tdW5pdGllc19zdW1tYXJ5X2xvbmcKYGBgCgpMb2FkIHRyYWl0IGRhdGEKYGBge3J9CnRyYWl0cyA9IHJlYWRfY3N2KGZpbGVuYW1lKFRBWE9OT01ZX09VVFBVVF9ESVIsICd0cmFpdHNfamV0ei5jc3YnKSkKaGVhZCh0cmFpdHMpCmBgYApMb2FkIHJlYWxtIGdlbwpgYGB7cn0Kc2Y6OnNmX3VzZV9zMihGQUxTRSkKcmVzb2x2ZSA9IHN0X3NpbXBsaWZ5KHN0X2J1ZmZlcihyZWFkX3NmKCcvVXNlcnMvamFtZXMvRHJvcGJveC9QaEQvRWNvcmVnaW9uczIwMTcvRWNvcmVnaW9uczIwMTcuc2hwJyksIDApLCBkVG9sZXJhbmNlID0gMC4wMikKaGVhZChyZXNvbHZlKQpgYGAKCiMgRXhhbWluZSBpbmRpdmlkdWFsIG1ldHJpY3MKYGBge3J9Cmdlb21fbm9ybWFsaXNlZF9oaXN0b2dyYW0gPSBmdW5jdGlvbihuYW1lLCBnZywgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikgewogIGdnICsgCiAgICBnZW9tX2hpc3RvZ3JhbShhZXMoZmlsbCA9IGNvcmVfcmVhbG0pLCBiaW53aWR0aCA9IDAuMSwgcG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gMC41KSwgY29sb3IgPSAiIzAwMDAwMCIsIHNpemUgPSAwLjQpICsKICAgIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSAwKSwgY29sb3IgPSAiIzAwMDAwMCIsIHNpemUgPSAwLjIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICAgIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSAxKSwgY29sb3IgPSAiIzAwMDAwMCIsIHNpemUgPSAwLjIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsgCiAgICB5bGFiKCJOdW1iZXIgb2YgY2l0aWVzIikgKyB4bGFiKCJOb3JtYWxpc2VkIFJlc3BvbnNlIikgKyB5bGltKGMoMCwgNzApKSArCiAgICBsYWJzKHRpdGxlID0gbmFtZSwgZmlsbCA9ICdSZWFsbScpICsKICAgIGZhY2V0X3dyYXAofiBpc191cmJhbl90aHJlc2hvbGQsIG5jb2w9MSkgKyAKICAgIHRoZW1lX2J3KCkgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPWxlZ2VuZC5wb3NpdGlvbikKfQpgYGAKCmBgYHtyfQpnZW9tX21hcCA9IGZ1bmN0aW9uKG1hcF9zZiwgdGl0bGUpIHsKICBub3JtX21udGRfYW5hbHlzaXNfZ2VvID0gZ2dwbG90KCkgKyAKICAgIGdlb21fc2YoZGF0YSA9IHdvcmxkX21hcCwgYWVzKGdlb21ldHJ5ID0gZ2VvbWV0cnkpKSArCiAgICBtYXBfc2YgKwogICAgbm9ybWFsaXNlZF9jb2xvdXJzX3NjYWxlICsKICAgIGxhYnModGl0bGUgPSB0aXRsZSwgY29sb3VyID0gJ05vcm1hbGlzZWRcblJlc3BvbnNlJykKfQpgYGAKCiMjIE1OVEQKYGBge3J9Cm5vcm1fbW50ZF9hbmFseXNpc19wbG90ID0gZ2VvbV9ub3JtYWxpc2VkX2hpc3RvZ3JhbSgKICAnTU5URCcsIAogIGdncGxvdChjb21tdW5pdHlfZGF0YV93aXRoX3JlYWxtLCBhZXMobW50ZF9ub3JtYWxpc2VkKSkKKQpub3JtX21udGRfYW5hbHlzaXNfcGxvdApgYGAKCmBgYHtyfQpub3JtX21udGRfYW5hbHlzaXNfZ2VvID0gZ2VvbV9tYXAoZ2VvbV9zZihkYXRhID0gY2l0eV9wb2ludHNfbG93LCBhZXMoY29sb3IgPSBtbnRkX25vcm1hbGlzZWQsIGdlb21ldHJ5ID0gZ2VvbWV0cnkpKSwgJ0xvdyB0aHJlc2hvbGQnKQpub3JtX21udGRfYW5hbHlzaXNfZ2VvCmBgYAoKYGBge3IsIGZpZy53aWR0aCA9IDQsIGZpZy5oZWlnaHQgPSA0fQpnZ2FycmFuZ2Uobm9ybV9tbnRkX2FuYWx5c2lzX3Bsb3QsIAogICAgICAgICAgZ2dhcnJhbmdlKAogICAgICAgICAgICBnZW9tX25vcm1hbGlzZWRfaGlzdG9ncmFtKCdNTlREJywgZ2dwbG90KGNvbW11bml0eV9kYXRhX3dpdGhfcmVhbG0sIGFlcyhtbnRkX25vcm1hbGlzZWQpKSwgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpLCAKICAgICAgICAgICAgZ2VvbV9tYXAoZ2VvbV9zZihkYXRhID0gY2l0eV9wb2ludHNfbWVkLCBhZXMoY29sb3IgPSBtbnRkX25vcm1hbGlzZWQsIGdlb21ldHJ5ID0gZ2VvbWV0cnkpKSwgJ01lZGl1bSB0aHJlc2hvbGQnKSwKICAgICAgICAgICAgZ2VvbV9tYXAoZ2VvbV9zZihkYXRhID0gY2l0eV9wb2ludHNfaGlnaCwgYWVzKGNvbG9yID0gbW50ZF9ub3JtYWxpc2VkLCBnZW9tZXRyeSA9IGdlb21ldHJ5KSksICdIaWdoIHRocmVzaG9sZCcpLAogICAgICAgICAgICBuY29sPTEsIGNvbW1vbi5sZWdlbmQgPSBUKSwKICAgICAgICAgIG5jb2wgPSAyKQpnZ3NhdmUoZmlsZW5hbWUoRklHVVJFU19PVVRQVVRfRElSLCAnbm9ybWFsaXNlZF9tbnRkLmpwZycpLCB3aWR0aCA9IDI1MDAsIGhlaWdodD0yNTAwLCB1bml0cyA9ICdweCcpCmBgYAoKIyMgRkQKYGBge3J9Cm5vcm1fZmRfYW5hbHlzaXNfcGxvdCA9IGdlb21fbm9ybWFsaXNlZF9oaXN0b2dyYW0oCiAgJ0ZEJywgCiAgZ2dwbG90KGNvbW11bml0eV9kYXRhX3dpdGhfcmVhbG0sIGFlcyhmZF9ub3JtYWxpc2VkKSkKKQpub3JtX2ZkX2FuYWx5c2lzX3Bsb3QKYGBgCgpgYGB7cn0Kbm9ybV9mZF9hbmFseXNpc19nZW8gPSBnZW9tX21hcChnZW9tX3NmKGRhdGEgPSBjaXR5X3BvaW50c19sb3csIGFlcyhjb2xvciA9IGZkX25vcm1hbGlzZWQsIGdlb21ldHJ5ID0gZ2VvbWV0cnkpKSwgJ0xvdyB0aHJlc2hvbGQnKQpub3JtX2ZkX2FuYWx5c2lzX2dlbwpgYGAKCmBgYHtyLCBmaWcud2lkdGggPSA0LCBmaWcuaGVpZ2h0ID0gNH0KZ2dhcnJhbmdlKGdlb21fbm9ybWFsaXNlZF9oaXN0b2dyYW0oJ0ZEJywgZ2dwbG90KGNvbW11bml0eV9kYXRhX3dpdGhfcmVhbG0sIGFlcyhmZF9ub3JtYWxpc2VkKSksIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSwgCiAgICAgICAgICBnZ2FycmFuZ2UoCiAgICAgICAgICAgIG5vcm1fZmRfYW5hbHlzaXNfZ2VvLCAKICAgICAgICAgICAgZ2VvbV9tYXAoZ2VvbV9zZihkYXRhID0gY2l0eV9wb2ludHNfbWVkLCBhZXMoY29sb3IgPSBmZF9ub3JtYWxpc2VkLCBnZW9tZXRyeSA9IGdlb21ldHJ5KSksICdNZWRpdW0gdGhyZXNob2xkJyksCiAgICAgICAgICAgIGdlb21fbWFwKGdlb21fc2YoZGF0YSA9IGNpdHlfcG9pbnRzX2hpZ2gsIGFlcyhjb2xvciA9IGZkX25vcm1hbGlzZWQsIGdlb21ldHJ5ID0gZ2VvbWV0cnkpKSwgJ0hpZ2ggdGhyZXNob2xkJyksCiAgICAgICAgICAgIG5jb2w9MSwgY29tbW9uLmxlZ2VuZCA9IFQpLAogICAgICAgICAgbmNvbCA9IDIpCmdnc2F2ZShmaWxlbmFtZShGSUdVUkVTX09VVFBVVF9ESVIsICdub3JtYWxpc2VkX2ZkLmpwZycpLCB3aWR0aCA9IDI1MDAsIGhlaWdodD0yNTAwLCB1bml0cyA9ICdweCcpCmBgYAoKIyMgRkQgLSBMb2NvbW90b3J5CmBgYHtyfQpub3JtX2ZkX2xvY29fYW5hbHlzaXNfcGxvdCA9IGdlb21fbm9ybWFsaXNlZF9oaXN0b2dyYW0oCiAgJ0ZEIExvY29tb3RvcnknLCAKICBnZ3Bsb3QoY29tbXVuaXR5X2RhdGFfd2l0aF9yZWFsbSwgYWVzKGxvY29tb3RvcnlfdHJhaXRfZmRfbm9ybWFsaXNlZCkpCikKbm9ybV9mZF9sb2NvX2FuYWx5c2lzX3Bsb3QKYGBgCgojIyBWQVIgLSBMb2NvbW90b3J5CmBgYHtyfQpub3JtX3Zhcl9sb2NvX2FuYWx5c2lzX3Bsb3QgPSBnZW9tX25vcm1hbGlzZWRfaGlzdG9ncmFtKAogICdWYXIgTG9jb21vdG9yeScsIAogIGdncGxvdChjb21tdW5pdHlfZGF0YV93aXRoX3JlYWxtLCBhZXMobG9jb21vdG9yeV90cmFpdF92YXJfbm9ybWFsaXNlZCkpCikKbm9ybV92YXJfbG9jb19hbmFseXNpc19wbG90CmBgYAoKIyMgRkQgLSBUcm9waGljCmBgYHtyfQpub3JtX2ZkX3Ryb3BoaWNfYW5hbHlzaXNfcGxvdCA9IGdlb21fbm9ybWFsaXNlZF9oaXN0b2dyYW0oCiAgJ0ZEIFRyb3BoaWMnLCAKICBnZ3Bsb3QoY29tbXVuaXR5X2RhdGFfd2l0aF9yZWFsbSwgYWVzKHRyb3BoaWNfdHJhaXRfZmRfbm9ybWFsaXNlZCkpCikKbm9ybV9mZF90cm9waGljX2FuYWx5c2lzX3Bsb3QKYGBgCgojIyBWQVIgLSBUcm9waGljCmBgYHtyfQpub3JtX3Zhcl90cm9waGljX2FuYWx5c2lzX3Bsb3QgPSBnZW9tX25vcm1hbGlzZWRfaGlzdG9ncmFtKAogICdWYXIgVHJvcGhpYycsIAogIGdncGxvdChjb21tdW5pdHlfZGF0YV93aXRoX3JlYWxtLCBhZXModHJvcGhpY190cmFpdF92YXJfbm9ybWFsaXNlZCkpCikKbm9ybV92YXJfdHJvcGhpY19hbmFseXNpc19wbG90CmBgYAoKIyMgRkQgLSBHYXBlIFdpZHRoCmBgYHtyfQpub3JtX2ZkX2dhcGVfYW5hbHlzaXNfcGxvdCA9IGdlb21fbm9ybWFsaXNlZF9oaXN0b2dyYW0oCiAgJ0ZEIEdhcGUgV2lkdGgnLCAKICBnZ3Bsb3QoY29tbXVuaXR5X2RhdGFfd2l0aF9yZWFsbSwgYWVzKGdhcGVfd2lkdGhfZmRfbm9ybWFsaXNlZCkpCikKbm9ybV9mZF9nYXBlX2FuYWx5c2lzX3Bsb3QKYGBgCgojIyBWQVIgLSBHYXBlIFdpZHRoCmBgYHtyfQpub3JtX3Zhcl9nYXBlX2FuYWx5c2lzX3Bsb3QgPSBnZW9tX25vcm1hbGlzZWRfaGlzdG9ncmFtKAogICdWYXIgR2FwZSBXaWR0aCcsIAogIGdncGxvdChjb21tdW5pdHlfZGF0YV93aXRoX3JlYWxtLCBhZXMoZ2FwZV93aWR0aF92YXJfbm9ybWFsaXNlZCkpCikKbm9ybV92YXJfZ2FwZV9hbmFseXNpc19wbG90CmBgYAoKIyMgRkQgLSBNYXNzCmBgYHtyfQpub3JtX2ZkX21hc3NfYW5hbHlzaXNfcGxvdCA9IGdlb21fbm9ybWFsaXNlZF9oaXN0b2dyYW0oCiAgJ0ZEIE1hc3MnLCAKICBnZ3Bsb3QoY29tbXVuaXR5X2RhdGFfd2l0aF9yZWFsbSwgYWVzKG1hc3NfZmRfbm9ybWFsaXNlZCkpCikKbm9ybV9mZF9tYXNzX2FuYWx5c2lzX3Bsb3QKYGBgCgojIyBWQVIgLSBNYXNzCmBgYHtyfQpub3JtX3Zhcl9tYXNzX2FuYWx5c2lzX3Bsb3QgPSBnZW9tX25vcm1hbGlzZWRfaGlzdG9ncmFtKAogICdWYXIgTWFzcycsIAogIGdncGxvdChjb21tdW5pdHlfZGF0YV93aXRoX3JlYWxtLCBhZXMobWFzc192YXJfbm9ybWFsaXNlZCkpCikKbm9ybV92YXJfbWFzc19hbmFseXNpc19wbG90CmBgYAoKIyBDb21wYXJlIG1ldHJpY3MgYWdhaW5zdCBlYWNoIG90aGVyCmBgYHtyfQpnZ3Bsb3QoY29tbXVuaXR5X2RhdGFfd2l0aF9yZWFsbSwgYWVzKHggPSBmZF9ub3JtYWxpc2VkLCB5ID0gbW50ZF9ub3JtYWxpc2VkLCBjb2xvdXIgPSBjb3JlX3JlYWxtKSkgKyAKICBnZW9tX3BvaW50KCkgKwogIHlsYWIoIk1OVEQiKSArIAogIHhsYWIoIkZEIikgKwogIHRoZW1lX2J3KCkgKwogIGZhY2V0X3dyYXAofiBpc191cmJhbl90aHJlc2hvbGQsIG5jb2wgPSAyKSArIGxhYnMoY29sb3IgPSAiUmVhbG0iKQpnZ3NhdmUoZmlsZW5hbWUoRklHVVJFU19PVVRQVVRfRElSLCAnbW50ZF9ieV9mZC5qcGcnKSkKYGBgCgpgYGB7cn0KbW50ZF9mZF9hbmFseXNpcyA9IGNvbW11bml0eV9kYXRhX3dpdGhfcmVhbG0gJT4lIAogIGRwbHlyOjpzZWxlY3QoY2l0eV9pZCwgaXNfdXJiYW5fdGhyZXNob2xkLCBtbnRkX25vcm1hbGlzZWQsIGZkX25vcm1hbGlzZWQpICU+JQogIGxlZnRfam9pbihjb21tdW5pdGllc19zdW1tYXJ5X2xvbmcpICU+JQogIG11dGF0ZSh1cmJhbl9wb29sX3BlcmMgPSB1cmJhbl9wb29sX3NpemUgKiAxMDAgLyByZWdpb25hbF9wb29sX3NpemUpCm1udGRfZmRfYW5hbHlzaXMKYGBgCgpgYGB7cn0KZ2dwYWlycyhtbnRkX2ZkX2FuYWx5c2lzICU+JSBkcGx5cjo6ZmlsdGVyKGlzX3VyYmFuX3RocmVzaG9sZCA9PSAnTG93JykgJT4lIGRwbHlyOjpzZWxlY3QobW50ZF9ub3JtYWxpc2VkLCBmZF9ub3JtYWxpc2VkLCByZWdpb25hbF9wb29sX3NpemUsIHVyYmFuX3Bvb2xfc2l6ZSwgdXJiYW5fcG9vbF9wZXJjKSkKYGBgCgpgYGB7cn0KZ2dwYWlycyhtbnRkX2ZkX2FuYWx5c2lzICU+JSBkcGx5cjo6ZmlsdGVyKGlzX3VyYmFuX3RocmVzaG9sZCA9PSAnTWVkaXVtJykgJT4lIGRwbHlyOjpzZWxlY3QobW50ZF9ub3JtYWxpc2VkLCBmZF9ub3JtYWxpc2VkLCByZWdpb25hbF9wb29sX3NpemUsIHVyYmFuX3Bvb2xfc2l6ZSwgdXJiYW5fcG9vbF9wZXJjKSkKZ2dzYXZlKGZpbGVuYW1lKEZJR1VSRVNfT1VUUFVUX0RJUiwgJ21udGRfYnlfZmRfcGFpcnNfbWVkaXVtLmpwZycpKQpgYGAKCmBgYHtyfQpnZ3BhaXJzKG1udGRfZmRfYW5hbHlzaXMgJT4lIGRwbHlyOjpmaWx0ZXIoaXNfdXJiYW5fdGhyZXNob2xkID09ICdIaWdoJykgJT4lIGRwbHlyOjpzZWxlY3QobW50ZF9ub3JtYWxpc2VkLCBmZF9ub3JtYWxpc2VkLCByZWdpb25hbF9wb29sX3NpemUsIHVyYmFuX3Bvb2xfc2l6ZSwgdXJiYW5fcG9vbF9wZXJjKSkKYGBgCgoKIyBQbG90IFVyYmFuL1JlZ2lvbmFsIFRyYWl0cwoKYGBge3J9CmNvbW11bml0aWVzX3dpdGhfdHJhaXRzID0gY29tbXVuaXRpZXMgJT4lIGxlZnRfam9pbih0cmFpdHMpICU+JSBsZWZ0X2pvaW4oY2l0eV90b19yZWFsbSkgJT4lIGZpbHRlcihjb3JlX3JlYWxtICE9ICdPY2VhbmlhJykKaGVhZChjb21tdW5pdGllc193aXRoX3RyYWl0cykKYGBgCgpgYGB7cn0KcmVhbG1zID0gdW5pcXVlKGNvbW11bml0aWVzX3dpdGhfdHJhaXRzJGNvcmVfcmVhbG0pCnJlYWxtcwpgYGAKCmBgYHtyfQpjb21tdW5pdGllc193aXRoX3RyYWl0c19sb25nID0gYmluZF9yb3dzKAogIGNvbW11bml0aWVzX3dpdGhfdHJhaXRzICU+JSBkcGx5cjo6c2VsZWN0KGdhcGVfd2lkdGgsIHRyb3BoaWNfdHJhaXQsIGxvY29tb3RvcnlfdHJhaXQsIG1hc3MsIGNvcmVfcmVhbG0pICU+JSBtdXRhdGUocmVjb3JkX3R5cGUgPSAnUmVnaW9uYWwnKSwKICBjb21tdW5pdGllc193aXRoX3RyYWl0cyAlPiUgZmlsdGVyKHByZXNlbnRfdXJiYW5fbG93KSAlPiUgZHBseXI6OnNlbGVjdChnYXBlX3dpZHRoLCB0cm9waGljX3RyYWl0LCBsb2NvbW90b3J5X3RyYWl0LCBtYXNzLCBjb3JlX3JlYWxtKSAlPiUgbXV0YXRlKHJlY29yZF90eXBlID0gJ1VyYmFuIExvdyBUaHJlc2hvbGQnKSwKICBjb21tdW5pdGllc193aXRoX3RyYWl0cyAlPiUgZmlsdGVyKHByZXNlbnRfdXJiYW5fbWVkKSAlPiUgZHBseXI6OnNlbGVjdChnYXBlX3dpZHRoLCB0cm9waGljX3RyYWl0LCBsb2NvbW90b3J5X3RyYWl0LCBtYXNzLCBjb3JlX3JlYWxtKSAlPiUgbXV0YXRlKHJlY29yZF90eXBlID0gJ1VyYmFuIE1lZGl1bSBUaHJlc2hvbGQnKSwKICBjb21tdW5pdGllc193aXRoX3RyYWl0cyAlPiUgZmlsdGVyKHByZXNlbnRfdXJiYW5faGlnaCkgJT4lIGRwbHlyOjpzZWxlY3QoZ2FwZV93aWR0aCwgdHJvcGhpY190cmFpdCwgbG9jb21vdG9yeV90cmFpdCwgbWFzcywgY29yZV9yZWFsbSkgJT4lIG11dGF0ZShyZWNvcmRfdHlwZSA9ICdVcmJhbiBIaWdoIFRocmVzaG9sZCcpCikKY29tbXVuaXRpZXNfd2l0aF90cmFpdHNfbG9uZyRyZWNvcmRfdHlwZSA9IGZhY3Rvcihjb21tdW5pdGllc193aXRoX3RyYWl0c19sb25nJHJlY29yZF90eXBlLCBsZXZlbHMgPSBjKCdSZWdpb25hbCcsICdVcmJhbiBMb3cgVGhyZXNob2xkJywgJ1VyYmFuIE1lZGl1bSBUaHJlc2hvbGQnLCAnVXJiYW4gSGlnaCBUaHJlc2hvbGQnKSkKCmhlYWQoY29tbXVuaXRpZXNfd2l0aF90cmFpdHNfbG9uZykKYGBgCgpgYGB7cn0KY29udmV4X2h1bGxfbG9jb190cm9waGljX3Blcl9yZWFsbSA9IGZ1bmN0aW9uKGZpbHRlcmVkX2RmLCByZWFsbXMpIHsKICByZXN1bHQgPSBkYXRhLmZyYW1lKCkKICAKICBmb3IgKHJlYWxtIGluIHJlYWxtcykgewogICAgcmVzdWx0ID0gcmJpbmQocmVzdWx0LCAKICAgICAgZmlsdGVyZWRfZGYgJT4lIAogICAgICAgIGZpbHRlcihjb3JlX3JlYWxtID09IHJlYWxtKSAlPiUgCiAgICAgICAgc2xpY2UoY2h1bGwodHJvcGhpY190cmFpdCwgbG9jb21vdG9yeV90cmFpdCkpICU+JSAKICAgICAgICBkcGx5cjo6c2VsZWN0KHRyb3BoaWNfdHJhaXQsIGxvY29tb3RvcnlfdHJhaXQpICU+JQogICAgICAgIG11dGF0ZShjb3JlX3JlYWxtID0gcmVhbG0pCiAgICApCiAgfQogIAogIHJlc3VsdAp9CmBgYAoKYGBge3J9CnJlZ2lvbmFsX2h1bGwgPSBjb252ZXhfaHVsbF9sb2NvX3Ryb3BoaWNfcGVyX3JlYWxtKGNvbW11bml0aWVzX3dpdGhfdHJhaXRzLCByZWFsbXMpCnVyYmFuX2h1bGxfaGlnaCA9IGNvbnZleF9odWxsX2xvY29fdHJvcGhpY19wZXJfcmVhbG0oY29tbXVuaXRpZXNfd2l0aF90cmFpdHMgJT4lIGZpbHRlcihwcmVzZW50X3VyYmFuX2hpZ2gpLCByZWFsbXMpCnVyYmFuX2h1bGxfbWVkID0gY29udmV4X2h1bGxfbG9jb190cm9waGljX3Blcl9yZWFsbShjb21tdW5pdGllc193aXRoX3RyYWl0cyAlPiUgZmlsdGVyKHByZXNlbnRfdXJiYW5fbWVkKSwgcmVhbG1zKQp1cmJhbl9odWxsX2xvdyA9IGNvbnZleF9odWxsX2xvY29fdHJvcGhpY19wZXJfcmVhbG0oY29tbXVuaXRpZXNfd2l0aF90cmFpdHMgJT4lIGZpbHRlcihwcmVzZW50X3VyYmFuX2xvdyksIHJlYWxtcykKCmdncGxvdChkYXRhID0gY29tbXVuaXRpZXNfd2l0aF90cmFpdHNfbG9uZywgYWVzKHggPSB0cm9waGljX3RyYWl0LCB5ID0gbG9jb21vdG9yeV90cmFpdCkpICsKICBnZW9tX3BvbHlnb24oZGF0YSA9IHJlZ2lvbmFsX2h1bGwsIGFscGhhID0gMC4xLCBmaWxsID0gImdyZWVuIiwgY29sb3I9ImdyZWVuIikgKwogIGdlb21fcG9seWdvbihkYXRhID0gdXJiYW5faHVsbF9sb3csIGFscGhhID0gMC4xNSwgZmlsbCA9ICJ5ZWxsb3ciLCBjb2xvcj0ieWVsbG93IikgKwogIGdlb21fcG9seWdvbihkYXRhID0gdXJiYW5faHVsbF9tZWQsIGFscGhhID0gMC4yLCBmaWxsID0gIm9yYW5nZSIsIGNvbG9yID0gIm9yYW5nZSIpICsKICBnZW9tX3BvbHlnb24oZGF0YSA9IHVyYmFuX2h1bGxfaGlnaCwgYWxwaGEgPSAwLjI1LCBmaWxsID0gInJlZCIsIGNvbG9yID0gInJlZCIpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvdXIgPSByZWNvcmRfdHlwZSkpICsKICB0aGVtZV9idygpICsgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCJncmVlbiIsICJ5ZWxsb3ciLCAib3JhbmdlIiwgInJlZCIpKSArCiAgeGxhYignVHJvcGhpYyBUcmFpdCcpICsgeWxhYignTG9jb21vdG9yeSBUcmFpdCcpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKSArIAogIGZhY2V0X3dyYXAofiBjb3JlX3JlYWxtKQpnZ3NhdmUoZmlsZW5hbWUoRklHVVJFU19PVVRQVVRfRElSLCAndHJhaXRzX2J5X3JlYWxtX2xvY29fdHJvcGhpYy5qcGcnKSkKYGBgCmBgYHtyfQpjb252ZXhfaHVsbF9nYXBlX21hc3NfcGVyX3JlYWxtID0gZnVuY3Rpb24oZmlsdGVyZWRfZGYsIHJlYWxtcykgewogIHJlc3VsdCA9IGRhdGEuZnJhbWUoKQogIAogIGZvciAocmVhbG0gaW4gcmVhbG1zKSB7CiAgICByZXN1bHQgPSByYmluZChyZXN1bHQsIAogICAgICBmaWx0ZXJlZF9kZiAlPiUgCiAgICAgICAgZmlsdGVyKGNvcmVfcmVhbG0gPT0gcmVhbG0pICU+JSAKICAgICAgICBzbGljZShjaHVsbChnYXBlX3dpZHRoLCBtYXNzKSkgJT4lIAogICAgICAgIGRwbHlyOjpzZWxlY3QoZ2FwZV93aWR0aCwgbWFzcykgJT4lCiAgICAgICAgbXV0YXRlKGNvcmVfcmVhbG0gPSByZWFsbSkKICAgICkKICB9CiAgCiAgcmVzdWx0Cn0KYGBgCgpgYGB7cn0KcmVnaW9uYWxfaHVsbF9nbSA9IGNvbnZleF9odWxsX2dhcGVfbWFzc19wZXJfcmVhbG0oY29tbXVuaXRpZXNfd2l0aF90cmFpdHMsIHJlYWxtcykKdXJiYW5faHVsbF9oaWdoX2dtID0gY29udmV4X2h1bGxfZ2FwZV9tYXNzX3Blcl9yZWFsbShjb21tdW5pdGllc193aXRoX3RyYWl0cyAlPiUgZmlsdGVyKHByZXNlbnRfdXJiYW5faGlnaCksIHJlYWxtcykKdXJiYW5faHVsbF9tZWRfZ20gPSBjb252ZXhfaHVsbF9nYXBlX21hc3NfcGVyX3JlYWxtKGNvbW11bml0aWVzX3dpdGhfdHJhaXRzICU+JSBmaWx0ZXIocHJlc2VudF91cmJhbl9tZWQpLCByZWFsbXMpCnVyYmFuX2h1bGxfbG93X2dtID0gY29udmV4X2h1bGxfZ2FwZV9tYXNzX3Blcl9yZWFsbShjb21tdW5pdGllc193aXRoX3RyYWl0cyAlPiUgZmlsdGVyKHByZXNlbnRfdXJiYW5fbG93KSwgcmVhbG1zKQoKZ2dwbG90KGRhdGEgPSBjb21tdW5pdGllc193aXRoX3RyYWl0c19sb25nLCBhZXMoeCA9IGdhcGVfd2lkdGgsIHkgPSBtYXNzKSkgKwogIGdlb21fcG9seWdvbihkYXRhID0gcmVnaW9uYWxfaHVsbF9nbSwgYWxwaGEgPSAwLjEsIGZpbGwgPSAiZ3JlZW4iLCBjb2xvcj0iZ3JlZW4iKSArCiAgZ2VvbV9wb2x5Z29uKGRhdGEgPSB1cmJhbl9odWxsX2xvd19nbSwgYWxwaGEgPSAwLjE1LCBmaWxsID0gInllbGxvdyIsIGNvbG9yPSJ5ZWxsb3ciKSArCiAgZ2VvbV9wb2x5Z29uKGRhdGEgPSB1cmJhbl9odWxsX21lZF9nbSwgYWxwaGEgPSAwLjIsIGZpbGwgPSAib3JhbmdlIiwgY29sb3IgPSAib3JhbmdlIikgKwogIGdlb21fcG9seWdvbihkYXRhID0gdXJiYW5faHVsbF9oaWdoX2dtLCBhbHBoYSA9IDAuMjUsIGZpbGwgPSAicmVkIiwgY29sb3IgPSAicmVkIikgKwogIGdlb21fcG9pbnQoYWVzKGNvbG91ciA9IHJlY29yZF90eXBlKSkgKwogIHRoZW1lX2J3KCkgKyBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoImdyZWVuIiwgInllbGxvdyIsICJvcmFuZ2UiLCAicmVkIikpICsKICB4bGFiKCdHYXBlIFdpZHRoJykgKyB5bGFiKCdNYXNzJykgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpICsgCiAgZmFjZXRfd3JhcCh+IGNvcmVfcmVhbG0pCmdnc2F2ZShmaWxlbmFtZShGSUdVUkVTX09VVFBVVF9ESVIsICd0cmFpdHNfYnlfcmVhbG1fZ2FwZV9tYXNzLmpwZycpKQpgYGAKCiMgUGxvdCBVcmJhbi9SZWdpb25hbCBQaHlsb2dlbnkKYGBge3J9CnBoeWxvX3RyZWUgPSByZWFkLnRyZWUoZmlsZW5hbWUoVEFYT05PTVlfT1VUUFVUX0RJUiwgJ3BoeWxvZ2VueS50cmUnKSkKZ2d0cmVlKHBoeWxvX3RyZWUsIGxheW91dD0nY2lyY3VsYXInKQpgYGAKCmBgYHtyfQpwbG90X3BoeWxvID0gZnVuY3Rpb24ocmVhbG0sIGNvbW11bml0eV9kZiA9IGNvbW11bml0aWVzX3dpdGhfdHJhaXRzKSB7CiAgc3BlY2llc19kZiA9IGNvbW11bml0eV9kZiAlPiUgZmlsdGVyKGNvcmVfcmVhbG0gPT0gcmVhbG0pICU+JSBncm91cF9ieShqZXR6X3NwZWNpZXNfbmFtZSkgJT4lIHN1bW1hcmlzZSgKICAgIHJlZ2lvbmFsX3Bvb2xzID0gbigpLAogICAgdXJiYW5fcG9vbHNfaGlnaCA9IHN1bShwcmVzZW50X3VyYmFuX2hpZ2gpLAogICAgdXJiYW5fcG9vbHNfbWVkaXVtID0gc3VtKHByZXNlbnRfdXJiYW5fbWVkKSwKICAgIHVyYmFuX3Bvb2xzX2xvdyA9IHN1bShwcmVzZW50X3VyYmFuX2xvdykKICApICU+JSBtdXRhdGUoCiAgICB1cmJhbl90b2xlcmVuY2VfaGlnaCA9IHVyYmFuX3Bvb2xzX2hpZ2ggLyByZWdpb25hbF9wb29scywKICAgIHVyYmFuX3RvbGVyZW5jZV9tZWQgPSB1cmJhbl9wb29sc19tZWRpdW0gLyByZWdpb25hbF9wb29scywKICAgIHVyYmFuX3RvbGVyZW5jZV9sb3cgPSB1cmJhbl9wb29sc19sb3cgLyByZWdpb25hbF9wb29scwogICkKICAKICB0cmVlX2Nyb3BwZWQgPC0gbGFkZGVyaXplKGRyb3AudGlwKHBoeWxvX3RyZWUsIHNldGRpZmYocGh5bG9fdHJlZSR0aXAubGFiZWwsIHNwZWNpZXNfZGYkamV0el9zcGVjaWVzX25hbWUpKSkKICAKICBwID0gZ2d0cmVlKHRyZWVfY3JvcHBlZCkgKyBnZW9tX3RpcGxhYihhbGlnbj1UKSAKICBwMSA9IGZhY2V0X3Bsb3QocCwgcGFuZWw9J0hpZ2gnLCBkYXRhPXNwZWNpZXNfZGYsIGdlb209Z2VvbV9zZWdtZW50LCBhZXMoeD0wLCB4ZW5kPXVyYmFuX3RvbGVyZW5jZV9oaWdoLCB5PXksIHllbmQ9eSksIHNpemU9MywgY29sb3I9J3JlZCcpIAogIHAyID0gZmFjZXRfcGxvdChwMSwgcGFuZWw9J01lZGl1bScsIGRhdGE9c3BlY2llc19kZiwgZ2VvbT1nZW9tX3NlZ21lbnQsIGFlcyh4PTAsIHhlbmQ9dXJiYW5fdG9sZXJlbmNlX21lZCwgeT15LCB5ZW5kPXkpLCBzaXplPTMsIGNvbG9yPSdvcmFuZ2UnKSAKICBwMyA9IGZhY2V0X3Bsb3QocDIsIHBhbmVsPSdMb3cnLCBkYXRhPXNwZWNpZXNfZGYsIGdlb209Z2VvbV9zZWdtZW50LCBhZXMoeD0wLCB4ZW5kPXVyYmFuX3RvbGVyZW5jZV9sb3csIHk9eSwgeWVuZD15KSwgc2l6ZT0zLCBjb2xvcj0neWVsbG93JykgCiAgCiAgZmFjZXRfd2lkdGhzKHAzICsgeGxpbV90cmVlKDYwKSArIHRoZW1lX3RyZWUyKCksIGMoVHJlZSA9IDUpKSArIGxhYnModGl0bGUgPSByZWFsbSwgc3VidGl0bGUgPSAnVXJiYW4gdG9sZXJhbmNlJykKfQpgYGAKCmBgYHtyfQpyZWFsbXMKYGBgCgpgYGB7cn0KcGxvdF9waHlsbygnTmVhcmN0aWMnKQpnZ3NhdmUoZmlsZW5hbWUoRklHVVJFU19PVVRQVVRfRElSLCAncGh5bG9nZW55X25lYXJjdGljLmpwZycpKQpgYGAKCmBgYHtyLCBmaWcuaGVpZ2h0ID0gMTB9CnBsb3RfcGh5bG8oJ05lb3Ryb3BpYycpCmdnc2F2ZShmaWxlbmFtZShGSUdVUkVTX09VVFBVVF9ESVIsICdwaHlsb2dlbnlfbmVvdHJvcGljLmpwZycpLCB3aWR0aD0yMTg3LCB1bml0cz0icHgiKQpgYGAKCmBgYHtyfQpwbG90X3BoeWxvKCdQYWxlYXJjdGljJykKZ2dzYXZlKGZpbGVuYW1lKEZJR1VSRVNfT1VUUFVUX0RJUiwgJ3BoeWxvZ2VueV9wYWxlYXJjdGljLmpwZycpKQpgYGAKCmBgYHtyfQpwbG90X3BoeWxvKCdBZnJvdHJvcGljJykKZ2dzYXZlKGZpbGVuYW1lKEZJR1VSRVNfT1VUUFVUX0RJUiwgJ3BoeWxvZ2VueV9hZnJvdHJvcGljLmpwZycpKQpgYGAKCmBgYHtyfQpwbG90X3BoeWxvKCdBdXN0cmFsYXNpYScpCmdnc2F2ZShmaWxlbmFtZShGSUdVUkVTX09VVFBVVF9ESVIsICdwaHlsb2dlbnlfYXVzdHJhbGFzaWEuanBnJykpCmBgYAoKIyBTcGVjaWVzIGluIGNvbW11bml0aWVzCkl0IHNlZW1zIHJlYXNvbmFibGUgdG8gZXhwZWN0IHRoYXQgY2l0aWVzIHdpdGggc2ltaWFsciByZWdpb25hbCBwb29scyB3aWxsIGhhdmUgc2ltaWxhciBzcGVjaWVzIGVudGVyaW5nIHRoZSBjaXR5LCBhbmQgdGh1cyBhIHNpbWlsYXIgcmVzcG9uc2UgdG8gdXJiYW5pc2F0aW9uLgoKYGBge3J9CnRvX3NwZWNpZXNfbWF0cml4ID0gZnVuY3Rpb24oZmlsdGVyZWRfY29tbXVuaXRpZXMpIHsKICBmaWx0ZXJlZF9jb21tdW5pdGllcyAlPiUgCiAgICBkcGx5cjo6c2VsZWN0KGNpdHlfaWQsIGpldHpfc3BlY2llc19uYW1lKSAlPiUgCiAgICBkaXN0aW5jdCgpICU+JQogICAgbXV0YXRlKHByZXNlbnQgPSBUUlVFKSAlPiUgCiAgICBwaXZvdF93aWRlcigKICAgICAgbmFtZXNfZnJvbSA9IGpldHpfc3BlY2llc19uYW1lLCAKICAgICAgdmFsdWVzX2Zyb20gPSAicHJlc2VudCIsIAogICAgICB2YWx1ZXNfZmlsbCA9IGxpc3QocHJlc2VudCA9IEYpCiAgICApICU+JSAKICAgIHRpYmJsZTo6Y29sdW1uX3RvX3Jvd25hbWVzKHZhcj0nY2l0eV9pZCcpCn0KYGBgCgpgYGB7cn0KY29tbXVuaXR5X25tZHMgPSBmdW5jdGlvbihmaWx0ZXJlZF9jb21tdW5pdGllcykgewogIHNwZWNpZXNfbWF0cml4ID0gdG9fc3BlY2llc19tYXRyaXgoZmlsdGVyZWRfY29tbXVuaXRpZXMpCiAgbm1kcyA8LSBtZXRhTURTKHNwZWNpZXNfbWF0cml4LCBrPTIsIHRyeW1heCA9IDMwKQogIG5tZHNfcmVzdWx0ID0gZGF0YS5mcmFtZShzY29yZXMobm1kcykpCiAgbm1kc19yZXN1bHQkY2l0eV9pZCA9IGFzLmRvdWJsZShyb3duYW1lcyhzY29yZXMobm1kcykpKQogIHJvd25hbWVzKG5tZHNfcmVzdWx0KSA9IE5VTEwKICAKICBubWRzX3Jlc3VsdCAlPiUKICAgIGxlZnRfam9pbihjaXR5X3BvaW50c1ssYygnY2l0eV9pZCcsICdjaXR5X25tJyldKQp9CmBgYAoKYGBge3J9CnBsb3Rfbm1kcyA9IGZ1bmN0aW9uKHBsb3RfZGF0YSwgY2x1c3RlcnMsIHJlYWxtLCBwbG90X2lzX3VyYmFuX3RocmVzaG9sZCkgewogIHBkID0gcGxvdF9kYXRhICU+JQogICAgbGVmdF9qb2luKGNvbW11bml0eV9kYXRhKSAlPiUKICAgIGZpbHRlcihpc191cmJhbl90aHJlc2hvbGQgPT0gcGxvdF9pc191cmJhbl90aHJlc2hvbGQpCiAgCiAgZ2dwbG90KHBkLCBhZXMoeCA9IE5NRFMxLCB5ID0gTk1EUzIpKSArIAogICAgZ2VvbV9wb2x5Z29uKGRhdGEgPSBjbHVzdGVycywgYWVzKGZpbGwgPSBjbHVzdGVyKSwgYWxwaGEgPSAwLjIpICsKICAgIGdlb21faml0dGVyKGFlcyhjb2xvdXIgPSBtbnRkX25vcm1hbGlzZWQsIHNpemUgPSBmZF9ub3JtYWxpc2VkKSkgKwogICAgbm9ybWFsaXNlZF9jb2xvdXJzX3NjYWxlICsKICAgIGdlb21fdGV4dF9yZXBlbChhZXMobGFiZWwgPSBjaXR5X25tKSwgbWF4Lm92ZXJsYXBzID0gMjUpICsKICAgIGxhYnMoCiAgICAgIHRpdGxlID0gcmVhbG0sIAogICAgICBzdWJ0aXRsZSA9IHBhc3RlKCdSZWdpb25hbCBwb29sLCB3aXRoIHVyYmFuIGNvbW11bml0eSB2YWx1ZXMgdXNpbmcnLCBwbG90X2lzX3VyYmFuX3RocmVzaG9sZCwgJ3RocmVzaG9sZCcpLCAKICAgICAgY29sb3IgPSAnTU5URCBOb3JtYWxpc2VkJywgCiAgICAgIHNpemUgPSAnRkQgTm9ybWFsaXNlZCcpCn0KYGBgCgpodHRwczovL3d3dy5kYXRhY2FtcC5jb20vdHV0b3JpYWwvay1tZWFucy1jbHVzdGVyaW5nLXIKYGBge3J9CnNjcmVlX3Bsb3QgPSBmdW5jdGlvbihjb21tdW5pdHlfbm1kc19kYXRhKSB7CiAgIyBEZWNpZGUgaG93IG1hbnkgY2x1c3RlcnMgdG8gbG9vayBhdAogIG5fY2x1c3RlcnMgPC0gMTAKICAKICAjIEluaXRpYWxpemUgdG90YWwgd2l0aGluIHN1bSBvZiBzcXVhcmVzIGVycm9yOiB3c3MKICB3c3MgPC0gbnVtZXJpYyhuX2NsdXN0ZXJzKQogIAogIHNldC5zZWVkKDEyMykKICAKICAjIExvb2sgb3ZlciAxIHRvIG4gcG9zc2libGUgY2x1c3RlcnMKICBmb3IgKGkgaW4gMTpuX2NsdXN0ZXJzKSB7CiAgICAjIEZpdCB0aGUgbW9kZWw6IGttLm91dAogICAga20ub3V0IDwtIGttZWFucyhjb21tdW5pdHlfbm1kc19kYXRhWyxjKCdOTURTMScsJ05NRFMyJyldLCBjZW50ZXJzID0gaSwgbnN0YXJ0ID0gMjApCiAgICAjIFNhdmUgdGhlIHdpdGhpbiBjbHVzdGVyIHN1bSBvZiBzcXVhcmVzCiAgICB3c3NbaV0gPC0ga20ub3V0JHRvdC53aXRoaW5zcwogIH0KICAKICAjIFByb2R1Y2UgYSBzY3JlZSBwbG90CiAgd3NzX2RmIDwtIHRpYmJsZShjbHVzdGVycyA9IDE6bl9jbHVzdGVycywgd3NzID0gd3NzKQogICAKICBzY3JlZV9wbG90IDwtIGdncGxvdCh3c3NfZGYsIGFlcyh4ID0gY2x1c3RlcnMsIHkgPSB3c3MsIGdyb3VwID0gMSkpICsKICAgICAgZ2VvbV9wb2ludChzaXplID0gNCkrCiAgICAgIGdlb21fbGluZSgpICsKICAgICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IGMoMiwgNCwgNiwgOCwgMTApKSArCiAgICAgIHhsYWIoJ051bWJlciBvZiBjbHVzdGVycycpCiAgc2NyZWVfcGxvdAp9CmBgYAoKYGBge3J9CmNvbnZleF9odWxsX2J5X2NsdXN0ZXIgPSBmdW5jdGlvbihjb21tdW5pdHlfZGYpIHsKICByZXN1bHQgPSBkYXRhLmZyYW1lKCkKICAKICBjbHVzdGVycyA9IHVuaXF1ZShjb21tdW5pdHlfZGYkY2x1c3RlcikKICBmb3IgKGNsdXN0ZXJfaSBpbiBjbHVzdGVycykgewogICAgcmVzdWx0ID0gcmJpbmQocmVzdWx0LCAKICAgICAgY29tbXVuaXR5X2RmICU+JSAKICAgICAgICBmaWx0ZXIoY2x1c3RlciA9PSBjbHVzdGVyX2kpICU+JSAKICAgICAgICBzbGljZShjaHVsbChOTURTMSwgTk1EUzIpKSAlPiUgCiAgICAgICAgZHBseXI6OnNlbGVjdChOTURTMSwgTk1EUzIsIGNsdXN0ZXIpIAogICAgKQogIH0KICAKICByZXN1bHQKfQpgYGAKCgojIyBOZWFyY3RpYwpgYGB7cn0KbmVhcmN0aWNfY29tbXVuaXRpZXMgPSBjb21tdW5pdHlfbm1kcyhjb21tdW5pdGllc193aXRoX3RyYWl0cyAlPiUgZmlsdGVyKGNvcmVfcmVhbG0gPT0gJ05lYXJjdGljJykpICAgIApuZWFyY3RpY19jb21tdW5pdGllcwpgYGAKCmBgYHtyfQpzY3JlZV9wbG90KG5lYXJjdGljX2NvbW11bml0aWVzKQpgYGAKCmBgYHtyfQpuZWFyY3RpY19jbHVzdGVycyA8LSBrbWVhbnMobmVhcmN0aWNfY29tbXVuaXRpZXNbLGMoJ05NRFMxJywgJ05NRFMyJyldLCBjZW50ZXJzID0gMywgbnN0YXJ0ID0gMjApCm5lYXJjdGljX2NvbW11bml0aWVzJGNsdXN0ZXIgPSBhcy5mYWN0b3IobmVhcmN0aWNfY2x1c3RlcnMkY2x1c3RlcikKYGBgCgpgYGB7cn0KZ2dwbG90KG5lYXJjdGljX2NvbW11bml0aWVzLCBhZXMoeCA9IE5NRFMxLCB5ID0gTk1EUzIsIGNvbG91ciA9IGNsdXN0ZXIpKSArIGdlb21fcG9pbnQoKQpgYGAKCgpgYGB7cn0KbmVhcmN0aWNfY2x1c3Rlcl9odWxscyA9IGNvbnZleF9odWxsX2J5X2NsdXN0ZXIobmVhcmN0aWNfY29tbXVuaXRpZXMpCmBgYAoKYGBge3J9CnBsb3Rfbm1kcyhuZWFyY3RpY19jb21tdW5pdGllcywgbmVhcmN0aWNfY2x1c3Rlcl9odWxscywgJ05lYXJjdGljJywgJ0hpZ2gnKQpgYGAKCmBgYHtyfQpwbG90X25tZHMobmVhcmN0aWNfY29tbXVuaXRpZXMsIG5lYXJjdGljX2NsdXN0ZXJfaHVsbHMsICdOZWFyY3RpYycsICdNZWRpdW0nKQpgYGAKCmBgYHtyfQpwbG90X25tZHMobmVhcmN0aWNfY29tbXVuaXRpZXMsIG5lYXJjdGljX2NsdXN0ZXJfaHVsbHMsICdOZWFyY3RpYycsICdMb3cnKQpgYGAKYGBge3J9CnNwZWNpZXNfaW5fbmVhcnRpYyA9IG5lYXJjdGljX2NvbW11bml0aWVzICU+JSBkcGx5cjo6c2VsZWN0KGNpdHlfaWQsIGNsdXN0ZXIpICU+JSBsZWZ0X2pvaW4oY29tbXVuaXRpZXNfd2l0aF90cmFpdHMpCgpwbG90X3BoeWxvKCdOZWFyY3RpYycsIHNwZWNpZXNfaW5fbmVhcnRpYyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMSkpCmBgYApgYGB7cn0KcmVzb2x2ZV9uZWFyY3RpYyA9IHJlc29sdmUgJT4lIGZpbHRlcihSRUFMTSA9PSAnTmVhcmN0aWMnKQoKZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHJlc29sdmVfbmVhcmN0aWMsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5KSwgYWxwaGEgPSAwLjIpICsKICBnZW9tX3NmKGRhdGEgPSBuZWFyY3RpY19jb21tdW5pdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMSkgJT4lIGxlZnRfam9pbihjaXR5X3BvaW50c19tZWQpLCBhZXMoY29sb3IgPSBtbnRkX25vcm1hbGlzZWQsIGdlb21ldHJ5ID0gZ2VvbWV0cnkpKSArCiAgbm9ybWFsaXNlZF9jb2xvdXJzX3NjYWxlICsgdGhlbWVfYncoKQpgYGAKCmBgYHtyfQpwbG90X3BoeWxvKCdOZWFyY3RpYycsIHNwZWNpZXNfaW5fbmVhcnRpYyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMikpCmBgYApgYGB7cn0KZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHJlc29sdmVfbmVhcmN0aWMsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5KSwgYWxwaGEgPSAwLjIpICsKICBnZW9tX3NmKGRhdGEgPSBuZWFyY3RpY19jb21tdW5pdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMikgJT4lIGxlZnRfam9pbihjaXR5X3BvaW50c19tZWQpLCBhZXMoY29sb3IgPSBtbnRkX25vcm1hbGlzZWQsIGdlb21ldHJ5ID0gZ2VvbWV0cnkpKSArCiAgbm9ybWFsaXNlZF9jb2xvdXJzX3NjYWxlICsgdGhlbWVfYncoKQpgYGAKCmBgYHtyfQpwbG90X3BoeWxvKCdOZWFyY3RpYycsIHNwZWNpZXNfaW5fbmVhcnRpYyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMykpCmBgYAoKYGBge3J9CmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSByZXNvbHZlX25lYXJjdGljLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSksIGFscGhhID0gMC4yKSArCiAgZ2VvbV9zZihkYXRhID0gbmVhcmN0aWNfY29tbXVuaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDMpICU+JSBsZWZ0X2pvaW4oY2l0eV9wb2ludHNfbWVkKSwgYWVzKGNvbG9yID0gbW50ZF9ub3JtYWxpc2VkLCBnZW9tZXRyeSA9IGdlb21ldHJ5KSkgKwogIG5vcm1hbGlzZWRfY29sb3Vyc19zY2FsZSArIHRoZW1lX2J3KCkKYGBgCgojIyBOZW90cm9waWMKYGBge3J9Cm5lb3Ryb3BpY19jb21tdW5pdGllcyA9IGNvbW11bml0eV9ubWRzKGNvbW11bml0aWVzX3dpdGhfdHJhaXRzICU+JSBmaWx0ZXIoY29yZV9yZWFsbSA9PSAnTmVvdHJvcGljJykpICAgIApuZW90cm9waWNfY29tbXVuaXRpZXMKYGBgCgpgYGB7cn0Kc2NyZWVfcGxvdChuZW90cm9waWNfY29tbXVuaXRpZXMpCmBgYAoKYGBge3J9Cm5lb3Ryb3BpY19jbHVzdGVycyA8LSBrbWVhbnMobmVvdHJvcGljX2NvbW11bml0aWVzWyxjKCdOTURTMScsICdOTURTMicpXSwgY2VudGVycyA9IDMsIG5zdGFydCA9IDIwKQpuZW90cm9waWNfY29tbXVuaXRpZXMkY2x1c3RlciA9IGFzLmZhY3RvcihuZW90cm9waWNfY2x1c3RlcnMkY2x1c3RlcikKYGBgCgpgYGB7cn0KZ2dwbG90KG5lb3Ryb3BpY19jb21tdW5pdGllcywgYWVzKHggPSBOTURTMSwgeSA9IE5NRFMyLCBjb2xvdXIgPSBjbHVzdGVyKSkgKyBnZW9tX3BvaW50KCkKYGBgCgoKYGBge3J9Cm5lb3Ryb3BpY19jbHVzdGVyX2h1bGxzID0gY29udmV4X2h1bGxfYnlfY2x1c3RlcihuZW90cm9waWNfY29tbXVuaXRpZXMpCmBgYAoKCmBgYHtyfQpwbG90X25tZHMobmVvdHJvcGljX2NvbW11bml0aWVzLCBuZW90cm9waWNfY2x1c3Rlcl9odWxscywgJ05lb3Ryb3BpYycsICdIaWdoJykKYGBgCgoKYGBge3J9CnBsb3Rfbm1kcyhuZW90cm9waWNfY29tbXVuaXRpZXMsIG5lb3Ryb3BpY19jbHVzdGVyX2h1bGxzLCAnTmVvdHJvcGljJywgJ01lZGl1bScpCmBgYAoKYGBge3J9CnBsb3Rfbm1kcyhuZW90cm9waWNfY29tbXVuaXRpZXMsIG5lb3Ryb3BpY19jbHVzdGVyX2h1bGxzLCAnTmVvdHJvcGljJywgJ0xvdycpCmBgYAoKYGBge3J9CnNwZWNpZXNfaW5fbmVvdHJvcGljID0gbmVvdHJvcGljX2NvbW11bml0aWVzICU+JSBkcGx5cjo6c2VsZWN0KGNpdHlfaWQsIGNsdXN0ZXIpICU+JSBsZWZ0X2pvaW4oY29tbXVuaXRpZXNfd2l0aF90cmFpdHMpCgpwbG90X3BoeWxvKCdOZW90cm9waWMnLCBzcGVjaWVzX2luX25lb3Ryb3BpYyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMSkpCmBgYAoKYGBge3J9CnJlc29sdmVfbmVvdHJvcGljID0gcmVzb2x2ZSAlPiUgZmlsdGVyKFJFQUxNID09ICdOZW90cm9waWMnKQoKZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHJlc29sdmVfbmVvdHJvcGljLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSksIGFscGhhID0gMC4yKSArCiAgZ2VvbV9zZihkYXRhID0gbmVvdHJvcGljX2NvbW11bml0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAxKSAlPiUgbGVmdF9qb2luKGNpdHlfcG9pbnRzX21lZCksIGFlcyhjb2xvciA9IG1udGRfbm9ybWFsaXNlZCwgZ2VvbWV0cnkgPSBnZW9tZXRyeSkpICsKICBub3JtYWxpc2VkX2NvbG91cnNfc2NhbGUgKyB0aGVtZV9idygpCmBgYAoKYGBge3J9CnBsb3RfcGh5bG8oJ05lb3Ryb3BpYycsIHNwZWNpZXNfaW5fbmVvdHJvcGljICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAyKSkKYGBgCgpgYGB7cn0KZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHJlc29sdmVfbmVvdHJvcGljLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSksIGFscGhhID0gMC4yKSArCiAgZ2VvbV9zZihkYXRhID0gbmVvdHJvcGljX2NvbW11bml0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAyKSAlPiUgbGVmdF9qb2luKGNpdHlfcG9pbnRzX21lZCksIGFlcyhjb2xvciA9IG1udGRfbm9ybWFsaXNlZCwgZ2VvbWV0cnkgPSBnZW9tZXRyeSkpICsKICBub3JtYWxpc2VkX2NvbG91cnNfc2NhbGUgKyB0aGVtZV9idygpCmBgYAoKYGBge3J9CnBsb3RfcGh5bG8oJ05lb3Ryb3BpYycsIHNwZWNpZXNfaW5fbmVvdHJvcGljICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAzKSkKYGBgCgpgYGB7cn0KZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHJlc29sdmVfbmVvdHJvcGljLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSksIGFscGhhID0gMC4yKSArCiAgZ2VvbV9zZihkYXRhID0gbmVvdHJvcGljX2NvbW11bml0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAzKSAlPiUgbGVmdF9qb2luKGNpdHlfcG9pbnRzX21lZCksIGFlcyhjb2xvciA9IG1udGRfbm9ybWFsaXNlZCwgZ2VvbWV0cnkgPSBnZW9tZXRyeSkpICsKICBub3JtYWxpc2VkX2NvbG91cnNfc2NhbGUgKyB0aGVtZV9idygpCmBgYAoKIyMgUGFsZWFyY3RpYwoKIyMgQWZyb3Ryb3BpYwoKIyMgSW5kb21hbGF5YW4KYGBge3J9CmluZG9tYWxheWFuX2NvbW11bml0aWVzID0gY29tbXVuaXR5X25tZHMoY29tbXVuaXRpZXNfd2l0aF90cmFpdHMgJT4lIGZpbHRlcihjb3JlX3JlYWxtID09ICdJbmRvbWFsYXlhbicpKSAgICAKaW5kb21hbGF5YW5fY29tbXVuaXRpZXMKYGBgCgpgYGB7cn0Kc2NyZWVfcGxvdChpbmRvbWFsYXlhbl9jb21tdW5pdGllcykKYGBgCgpgYGB7cn0KaW5kb21hbGF5YW5fY2x1c3RlcnMgPC0ga21lYW5zKGluZG9tYWxheWFuX2NvbW11bml0aWVzWyxjKCdOTURTMScsICdOTURTMicpXSwgY2VudGVycyA9IDQsIG5zdGFydCA9IDIwKQppbmRvbWFsYXlhbl9jb21tdW5pdGllcyRjbHVzdGVyID0gYXMuZmFjdG9yKGluZG9tYWxheWFuX2NsdXN0ZXJzJGNsdXN0ZXIpCmBgYAoKYGBge3J9CmdncGxvdChpbmRvbWFsYXlhbl9jb21tdW5pdGllcywgYWVzKHggPSBOTURTMSwgeSA9IE5NRFMyLCBjb2xvdXIgPSBjbHVzdGVyKSkgKyBnZW9tX3BvaW50KCkKYGBgCgpgYGB7cn0KaW5kb21hbGF5YW5fY2x1c3Rlcl9odWxscyA9IGNvbnZleF9odWxsX2J5X2NsdXN0ZXIoaW5kb21hbGF5YW5fY29tbXVuaXRpZXMpCmBgYAoKYGBge3J9CnBsb3Rfbm1kcyhpbmRvbWFsYXlhbl9jb21tdW5pdGllcywgaW5kb21hbGF5YW5fY2x1c3Rlcl9odWxscywgJ0luZG9tYWxheWFuJywgJ0hpZ2gnKQpgYGAKCmBgYHtyfQpwbG90X25tZHMoaW5kb21hbGF5YW5fY29tbXVuaXRpZXMsIGluZG9tYWxheWFuX2NsdXN0ZXJfaHVsbHMsICdJbmRvbWFsYXlhbicsICdNZWRpdW0nKQpgYGAKCmBgYHtyfQpwbG90X25tZHMoaW5kb21hbGF5YW5fY29tbXVuaXRpZXMsIGluZG9tYWxheWFuX2NsdXN0ZXJfaHVsbHMsICdJbmRvbWFsYXlhbicsICdMb3cnKQpgYGAKCmBgYHtyfQpzcGVjaWVzX2luX2luZG9tYWxheWFuID0gaW5kb21hbGF5YW5fY29tbXVuaXRpZXMgJT4lIGRwbHlyOjpzZWxlY3QoY2l0eV9pZCwgY2x1c3RlcikgJT4lIGxlZnRfam9pbihjb21tdW5pdGllc193aXRoX3RyYWl0cykKCnBsb3RfcGh5bG8oJ0luZG9tYWxheWFuJywgc3BlY2llc19pbl9pbmRvbWFsYXlhbiAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMSkpCmBgYAoKYGBge3J9CnJlc29sdmVfaW5kb21hbGF5YW4gPSByZXNvbHZlICU+JSBmaWx0ZXIoUkVBTE0gPT0gJ0luZG9tYWxheWFuJykKCmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSByZXNvbHZlX2luZG9tYWxheWFuLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSksIGFscGhhID0gMC4yKSArCiAgZ2VvbV9zZihkYXRhID0gaW5kb21hbGF5YW5fY29tbXVuaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDEpICU+JSBsZWZ0X2pvaW4oY2l0eV9wb2ludHNfbWVkKSwgYWVzKGNvbG9yID0gbW50ZF9ub3JtYWxpc2VkLCBnZW9tZXRyeSA9IGdlb21ldHJ5KSkgKwogIG5vcm1hbGlzZWRfY29sb3Vyc19zY2FsZSArIHRoZW1lX2J3KCkKYGBgCgpgYGB7cn0KcGxvdF9waHlsbygnSW5kb21hbGF5YW4nLCBzcGVjaWVzX2luX2luZG9tYWxheWFuICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAyKSkKYGBgCgpgYGB7cn0KZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHJlc29sdmVfaW5kb21hbGF5YW4sIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5KSwgYWxwaGEgPSAwLjIpICsKICBnZW9tX3NmKGRhdGEgPSBpbmRvbWFsYXlhbl9jb21tdW5pdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMikgJT4lIGxlZnRfam9pbihjaXR5X3BvaW50c19tZWQpLCBhZXMoY29sb3IgPSBtbnRkX25vcm1hbGlzZWQsIGdlb21ldHJ5ID0gZ2VvbWV0cnkpKSArCiAgbm9ybWFsaXNlZF9jb2xvdXJzX3NjYWxlICsgdGhlbWVfYncoKQpgYGAKCmBgYHtyfQpwbG90X3BoeWxvKCdJbmRvbWFsYXlhbicsIHNwZWNpZXNfaW5faW5kb21hbGF5YW4gJT4lIGZpbHRlcihjbHVzdGVyID09IDMpKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gcmVzb2x2ZV9pbmRvbWFsYXlhbiwgYWVzKGdlb21ldHJ5ID0gZ2VvbWV0cnkpLCBhbHBoYSA9IDAuMikgKwogIGdlb21fc2YoZGF0YSA9IGluZG9tYWxheWFuX2NvbW11bml0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAzKSAlPiUgbGVmdF9qb2luKGNpdHlfcG9pbnRzX21lZCksIGFlcyhjb2xvciA9IG1udGRfbm9ybWFsaXNlZCwgZ2VvbWV0cnkgPSBnZW9tZXRyeSkpICsKICBub3JtYWxpc2VkX2NvbG91cnNfc2NhbGUgKyB0aGVtZV9idygpCmBgYAoKYGBge3J9CnBsb3RfcGh5bG8oJ0luZG9tYWxheWFuJywgc3BlY2llc19pbl9pbmRvbWFsYXlhbiAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gNCkpCmBgYAoKYGBge3J9CmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSByZXNvbHZlX2luZG9tYWxheWFuLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSksIGFscGhhID0gMC4yKSArCiAgZ2VvbV9zZihkYXRhID0gaW5kb21hbGF5YW5fY29tbXVuaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDQpICU+JSBsZWZ0X2pvaW4oY2l0eV9wb2ludHNfbWVkKSwgYWVzKGNvbG9yID0gbW50ZF9ub3JtYWxpc2VkLCBnZW9tZXRyeSA9IGdlb21ldHJ5KSkgKwogIG5vcm1hbGlzZWRfY29sb3Vyc19zY2FsZSArIHRoZW1lX2J3KCkKYGBgCgojIyBBdXN0cmFsYXNpYQo=